Expression Language (EL) Injection occurs when an application incorporates untrusted user input into strings that are later interpreted by an Expression Language engine. ELs are often used in Java EE frameworks (like JSF, JSP), Spring Expression Language (SPeL), and some templating systems to provide dynamic functionality. If user input containing EL syntax (e.g., ${...}, #{...}, *{...}) is evaluated, attackers can execute arbitrary code, access sensitive objects, or manipulate application behavior. ⚙️💉
Remote Code Execution (RCE): Attackers can often access underlying platform objects and methods, potentially executing arbitrary commands on the server.
Information Disclosure: Accessing sensitive application objects, configuration settings, or environment variables.
Logic Manipulation: Altering application flow or data by triggering unexpected method calls.
Denial of Service: Executing expressions that consume excessive resources.
Reference Details
CWE ID:CWE-917OWASP Top 10 (2021): A03:2021 - Injection
Severity: Critical (often leads to RCE)
This vulnerability is highly dependent on the specific framework and how it parses and evaluates expressions. Key defenses include:
Avoiding Evaluation of User Input: The safest approach is never to include untrusted data in strings that will be evaluated by an EL engine.
Using Safe EL Subsets/Contexts: Some frameworks allow configuring restricted EL contexts that limit access to dangerous objects or methods.
Input Sanitization/Escaping: Escaping EL control characters ($, #, {, }) in user input before it’s processed by the EL engine. This is less reliable than avoiding evaluation.
Keeping Frameworks Updated: Patches often fix EL injection vulnerabilities discovered in framework components.
Common in Java EE (JSF, JSP using ${} or #{}) and Spring applications using Spring Expression Language (SPeL - #{}, *{}). User input might end up in dynamically generated error messages, custom components, or unsafe template processing.
JSF/JSP: Avoid including raw user input in messages or components rendered on pages where EL is evaluated. If necessary, sanitize the input to remove or escape ${, #{, {, } characters. Use standard JSF components which often handle escaping.
SPeL: Do not construct SPeL expressions by concatenating user input. Use safe contexts (SimpleEvaluationContext) instead of StandardEvaluationContext if possible, as SimpleEvaluationContext restricts access to dangerous features like T() operator (type references) and bean references. If you must use user input, treat it strictly as data, not part of the expression logic (e.g., pass it as a variable to the context).
Identify all points where user input might be reflected in JSF/JSP pages or used in SpelExpressionParser.parseExpression(). Submit payloads designed to trigger EL evaluation:
OS commands (SPeL): #{T(java.lang.Runtime).getRuntime().exec('id')}
Check if the expressions are evaluated or if errors related to parsing/execution occur.
Less common than in Java, but can occur in templating engines like Jinja2 or Mako if user input is rendered unsafely within template structures or if unsafe methods like string.Formatter.format are used with format strings partially controlled by users.
Using str.format() where the format string itself contains placeholders derived from user input.
# utils/formatter.pyclass UserData: def __init__(self, name): self.name = name # DANGEROUS: Attacker might control other attributes or methods # accessed via the format string.def format_greeting(request): # Assume format_string comes from a less trusted source, e.g., customizable theme format_string = request.GET.get('format', "Hello {user.name}") user = UserData(request.GET.get('name', 'Guest')) try: # DANGEROUS: If format_string is "{user.__init__.__globals__[os].system('id')}" # this could lead to code execution. Requires specific object structure. return format_string.format(user=user) except Exception as e: return f"Format Error: {e}"
Templates: Never render template strings derived directly from user input. Always use secure methods like render_template('template.html', var=user_input) where user input is passed as a variable to a fixed template file. Ensure auto-escaping is enabled in the template engine.
String Formatting: Avoid using str.format() or f-strings where the format string itself contains untrusted input. Use simpler concatenation or pass user input as data arguments, not part of the format structure.
# views/render_template.py (Secure Jinja2 Usage)from django.shortcuts import render # Use Django's secure renderingdef render_secure_template(request): name = request.GET.get('name', 'Guest') # SECURE: User input is passed as context data to a fixed template file. # Jinja2 will escape 'name' by default within {{ name }}. return render(request, 'greeting_template.html', {'name': name})# templates/greeting_template.html// <h1>Hello {{ name }}</h1> // Auto-escaped by default in Django/Jinja2# utils/formatter.py (Secure Formatting)def format_greeting_secure(request): name = request.GET.get('name', 'Guest') # SECURE: Simple, safe string concatenation or f-string. # User input is treated purely as data. return f"Hello {name}"
Identify where user input might influence template file content or format strings. Test by injecting template syntax relevant to the engine (e.g., {{ 7*7 }}, {{ config }}, {{ self }} for Jinja2; {user.__class__} for str.format). Look for evaluated expressions, exposed objects, or errors revealing internal state.
Less common native EL, but vulnerabilities can arise from custom implementations, unsafe use of templating engines (like NVelocity, Fluid), or misuse of libraries that parse expression-like strings. Unsafe deserialization (BinaryFormatter) can also be a vector if it processes malicious objects designed to execute code during deserialization, sometimes triggered via expression evaluation within properties.
A hypothetical custom parser that mimics EL behavior unsafely.
// Services/CustomEvaluator.cs// NOTE: This is a highly simplified and contrived example.// Real-world scenarios often involve complex reflection or scripting engines.public object Evaluate(string expression, object context){ // DANGEROUS: Highly simplified example of evaluating simple property access. // A real attack might use reflection combined with expression parsing. // If expression = "context.GetType().Assembly.Load('System.Diagnostics').GetType('System.Diagnostics.Process').Start('calc.exe')" // this logic (if extended with reflection) could be vulnerable. if (expression.StartsWith("context.")) { var propertyName = expression.Substring(8); var property = context.GetType().GetProperty(propertyName); if (property != null) return property.GetValue(context); } return null; // Basic example}
Using a template engine like Fluid unsafely, allowing access to dangerous objects.
// Services/TemplateService.csusing Fluid; // Example using Fluid librarypublic string RenderTemplate(string templateString, object model){ // DANGEROUS: If templateString comes from user input and allows // access to .NET objects or methods unsafely. Fluid has security measures, // but misconfiguration or older versions could be vulnerable. // Input: templateString = "{{ System.Diagnostics.Process.Start('calc.exe') }}" // (This specific syntax might be blocked by default, but illustrates the risk) var parser = new FluidParser(); if (parser.TryParse(templateString, out var template, out var error)) { var context = new TemplateContext(model); // Add options to restrict member access if needed: // context.MemberAccessStrategy.Register<MyModel>(); // Allow only specific types // context.Options.AllowClr = false; // Disallow direct CLR access (Important!) return template.Render(context); } return $"Template Error: {error}";}
Avoid custom expression evaluators that use reflection on untrusted input.
When using template engines, understand their security model. Disable access to arbitrary .NET types/methods (AllowClr = false in Fluid). Register only safe types/members for access within templates. Always keep the library updated.
Never use insecure deserializers like BinaryFormatter.
// Services/TemplateService.cs (Secure Fluid Usage)using Fluid;public string RenderTemplateSecure(string templateString, object model){ var parser = new FluidParser(); if (parser.TryParse(templateString, out var template, out var error)) { var context = new TemplateContext(model); // SECURE: Disallow direct CLR access. context.Options.AllowClr = false; // SECURE: Only allow access to members of registered safe types (if needed). // context.MemberAccessStrategy.Register<MyViewModel>(); // context.MemberAccessStrategy.Register<SomeSafeType>(); return template.Render(context); } return $"Template Error: {error}";}
Identify where strings containing expression-like syntax are evaluated. If using templating libraries, check their documentation for security configuration (disabling CLR access, member access strategies). Test by providing payloads relevant to the engine, aiming to access system classes (System.Diagnostics.Process), read files, or execute commands.
Primarily occurs with Server-Side Template Injection (SSTI) in engines like Twig or Smarty if templates include user input in unsafe ways, or potentially via eval() if used within template logic (which is rare and highly discouraged).
Twig: Never create templates directly from user input using Environment::createTemplate() or ArrayLoader with user strings. Use FilesystemLoader to load fixed template files. Ensure auto-escaping is enabled. Use the Twig sandbox extension if rendering potentially untrusted template logic.
Smarty: Enable security settings ($smarty->enableSecurity()). Do not disable default protections. Avoid using {php} tags. Ensure user input is properly escaped ({$variable|escape}).
Server-Side Template Injection (SSTI) in engines like EJS, Handlebars, Pug, Nunjucks if user input is included in the template structure or rendered without proper escaping.
Always use the default escaped output tags:<%= ... %> in EJS, {{ ... }} in Handlebars.
Never use unescaped output tags (<%- ... %>, {{{ ... }}}) directly with user-controlled data unless you have explicitly sanitized it for that specific context (very rare).
Keep template engine libraries updated.
Configure template engines securely (e.g., disable prototype access in Handlebars if possible).
Avoid rendering template strings provided by users.
Identify all template rendering points where user input is displayed. Check if escaped (<%=, {{) or unescaped (<%-, {{{) tags are used. Submit payloads with the template engine’s syntax (<%= 7*7 %>, {{ 7*7 }}, {{this ...}}) into input fields reflected with unescaped tags. Look for evaluated expressions, exposed objects (like process), errors, or successful command execution.
Server-Side Template Injection (SSTI) in ERB, Slim, Haml if user input is evaluated within code execution delimiters (<% ... %>) or used to construct template paths/content dynamically. Unsafe use of methods like render inline: can also be a vector.
Vulnerable Scenario 1: User Input Inside ERB Code Tags
A developer mistakenly includes user input within <% ... %> instead of <%= ... %>.
<% # DANGEROUS: params[:sort_order] is executed as Ruby code. # Input: ?sort_order = "; Kernel.system('id'); #" (Closing existing command, executing new one) items = @items.order("price #{params[:sort_order]}")%>
# Simplified controller (May not be directly vulnerable this way often,# but illustrates code execution if input lands in <% %>)# This scenario is more likely if the *template file itself* is built dynamically.
<% # SECURE: Validate the sort order parameter against an allow-list. valid_orders = ['asc', 'desc'] sort_order = params[:sort_order].presence_in(valid_orders) || 'asc' # SECURE: Use the validated variable. items = @items.order("price #{sort_order}")%><p>You searched for: <%=h params[:q] %></p>
# app/controllers/preview_controller.rb (Secure)class PreviewController < ApplicationController def show # SECURE: Do not use render :inline with dynamic strings. # Instead, load a fixed template and pass data. @item_name = "Example Item" # Maybe load specific partial based on a *validated* param[:type] render :show # Renders app/views/preview/show.html.erb endend
Review ERB/Slim/Haml templates. Look for any instances where params[], @variable_from_params, or other user input is used inside code execution blocks (<% ... %>, - in Slim/Haml). Test endpoints using render :inline by injecting template code (<%= 7*7 %>, <% Kernel.system('id') %>) into parameters used to build the inline template string.