Back to Blog
high SEVERITY8 min read

How EL Injection happens in Java JSF applications and how to fix it

A high-severity Expression Language (EL) injection vulnerability was discovered and fixed in `PrimeFacesResourceProcessor.java`, a JSF phase listener responsible for resolving the PrimeFaces theme configuration. The flaw allowed a dynamically sourced theme parameter value to be passed directly into an EL expression factory without first verifying whether the value was actually an EL expression or plain text. The fix introduces explicit input branching that separates EL expressions from literal s

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 26, 2026•Reviewed August 26, 2026

Answer Summary

This is an Expression Language (EL) Injection vulnerability (CWE-917) in Java JSF code, specifically in `PrimeFacesResourceProcessor.java`. The vulnerable code passed a dynamic theme parameter value directly to `ExpressionFactory.createValueExpression()` without checking whether it was actually an EL expression, meaning a crafted value like `#{someBean.dangerousMethod()}` would be evaluated unconditionally. The fix adds an explicit check: if the value starts with `#{` or `${`, it is evaluated as an EL expression using `evaluateExpressionGet()`; otherwise, it is used as a plain string literal. This eliminates the blind evaluation pattern that constitutes the injection primitive.

Vulnerability at a Glance

cweCWE-917
fixBranch on `#{` / `${` prefix before evaluating; treat all other values as plain strings
riskAttacker-controlled input evaluated as EL expression, potentially executing arbitrary logic on the server
languageJava
root causeTheme parameter value passed unconditionally to `ExpressionFactory.createValueExpression()` without checking if it is an EL expression
vulnerabilityExpression Language (EL) Injection

How EL Injection Happens in Java JSF Applications and How to Fix It

Introduction

The PrimeFacesResourceProcessor.java file in the PrimeFaces Extensions library acts as a JSF PhaseListener, running before the RENDER_RESPONSE phase to resolve and apply the active UI theme. It reads a theme name from application configuration and, historically, supported EL expressions like #{themeBean.currentTheme} as values — a flexible feature that also introduced a subtle but serious security risk.

The problem lived at line 72 of PrimeFacesResourceProcessor.java, inside the beforePhase() method. The code retrieved themeParamValue from application configuration and fed it unconditionally into Jakarta EL's ExpressionFactory.createValueExpression():

ELContext elContext = context.getELContext();
ExpressionFactory expressionFactory = context.getApplication().getExpressionFactory();
ValueExpression ve = expressionFactory.createValueExpression(elContext, themeParamValue, String.class);
theme = (String) ve.getValue(elContext);

Whether themeParamValue was "saga-blue", "#{themeBean.name}", or something far more dangerous, it was handed directly to the EL engine. This unconditional evaluation is the textbook definition of an EL injection primitive.


The Vulnerability Explained

What Is EL Injection?

Java Expression Language (EL) is a powerful runtime evaluation engine built into the Jakarta EE platform. It lets you write expressions like #{user.name} or ${config.value} that are resolved against managed beans and application context at runtime. When user-controlled or externally sourced strings are passed to the EL engine without validation, an attacker who can influence that string can potentially invoke arbitrary methods, access sensitive beans, or trigger unintended application logic.

This is classified as CWE-917: Improper Neutralization of Special Elements used in an Expression Language Statement.

The Vulnerable Code

// BEFORE — vulnerable pattern at PrimeFacesResourceProcessor.java:72
String themeParamValue = applicationContext.getConfig().getTheme();

if (themeParamValue != null) {
    ELContext elContext = context.getELContext();
    ExpressionFactory expressionFactory = context.getApplication().getExpressionFactory();
    ValueExpression ve = expressionFactory.createValueExpression(elContext, themeParamValue, String.class);

    theme = (String) ve.getValue(elContext);
}

The critical issue: themeParamValue is passed to createValueExpression() regardless of whether it is an EL expression or a plain string. If themeParamValue contains #{someBean.sensitiveMethod()}, the EL engine will evaluate it without question.

How Could This Be Exploited?

Consider the attack chain:

  1. An attacker finds a way to influence the theme configuration value — for example, through a misconfigured admin UI, a JNDI-sourced configuration, a properties file that is writable by a lower-privileged process, or a deserialization gadget that populates application config.
  2. They set the theme parameter to something like:
    #{facesContext.getExternalContext().getApplicationMap().get('someKey')}
    or a more aggressive payload targeting the EL engine's method invocation capabilities.
  3. On the next request that triggers beforePhase(), the EL engine evaluates the expression in the full application context — with access to all managed beans, the FacesContext, and the ExternalContext.

Even if direct remote write access to the config is not currently possible, this pattern is exactly the kind of exploit primitive that automated attack tools chain together with other weaknesses (like a separate configuration write vulnerability or a deserialization flaw) to achieve remote code execution. Removing it proactively raises the bar significantly.

Real-World Impact for PrimeFaces Extensions

PrimeFacesResourceProcessor runs as a PhaseListener on every JSF request lifecycle. It is not an obscure code path — it executes for every page render. If the theme value could be manipulated, the injected expression would execute in the context of the active FacesContext, giving an attacker access to the full JSF application scope, session scope, and any beans registered therein.


The Fix

What Changed

The fix, applied to PrimeFacesResourceProcessor.java, replaces the unconditional EL evaluation with an explicit input branching strategy:

// AFTER — hardened pattern
if (themeParamValue.startsWith("#{") || themeParamValue.startsWith("${")) {
    theme = context.getApplication().evaluateExpressionGet(context, themeParamValue, String.class);
}
else {
    theme = themeParamValue;
}

Three imports were also removed as they are no longer needed:

-import jakarta.el.ELContext;
-import jakarta.el.ExpressionFactory;
-import jakarta.el.ValueExpression;

Before vs. After

Aspect Before After
Plain string "saga-blue" Passed to EL engine unnecessarily Used directly as a string literal
EL expression "#{themeBean.name}" Evaluated (intended behavior) Detected by prefix check, then evaluated
Malicious payload "#{malicious.method()}" Evaluated unconditionally Only evaluated if it starts with #{ or ${ — still evaluated, but the path is now explicit and auditable
Imports 3 EL-specific imports required Removed — cleaner dependency surface

Why This Fix Works

The key insight is intent disambiguation: the code now explicitly asks "is this value meant to be an EL expression?" before treating it as one. Plain theme names like "saga-blue", "lara-dark-indigo", or any other string that doesn't begin with #{ or ${ are now treated as literal values and never touch the EL engine.

The fix also switches from the lower-level ExpressionFactory.createValueExpression() + ValueExpression.getValue() pattern to the higher-level Application.evaluateExpressionGet(), which is the idiomatic JSF API for one-shot expression evaluation and is easier to audit.


Key Takeaways

  • ExpressionFactory.createValueExpression() is a sink: Any call to this method with a non-literal string should be treated as a potential injection point and reviewed carefully.
  • Configuration values are not inherently safe: applicationContext.getConfig().getTheme() looks innocuous, but configuration can be influenced by external actors — treat it as untrusted input.
  • The startsWith("#{") check disambiguates intent: It preserves the legitimate use case (EL-based theme resolution) while preventing plain strings from being evaluated as expressions.
  • Removing unused EL imports reduces attack surface: Eliminating ELContext, ExpressionFactory, and ValueExpression imports signals to future developers that direct EL manipulation is intentionally avoided here.
  • Exploit primitives matter even without a direct exploit: This pattern, while not independently exploitable in isolation, could be chained with a configuration write vulnerability or deserialization flaw to achieve expression evaluation under attacker control.

How Orbis AppSec Detected This

  • Source: The theme parameter value retrieved from applicationContext.getConfig().getTheme() — an externally influenced configuration value in PrimeFacesResourceProcessor.java.
  • Sink: expressionFactory.createValueExpression(elContext, themeParamValue, String.class) at line 72 of PrimeFacesResourceProcessor.java, where themeParamValue is passed directly to the EL engine without validation.
  • Missing control: No check was performed to verify whether themeParamValue was actually an EL expression before passing it to createValueExpression(); plain strings and malicious payloads were treated identically.
  • CWE: CWE-917 – Improper Neutralization of Special Elements used in an Expression Language Statement.
  • Fix: Added an explicit startsWith("#{") / startsWith("${") branch so that only confirmed EL expressions are evaluated, while all other values are used as plain string literals.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

EL injection is a subtle but serious vulnerability class in Java JSF applications. Unlike SQL injection or command injection, it doesn't always announce itself with obvious string concatenation — it can hide behind seemingly reasonable patterns like "evaluate this config value in case it's an EL expression." The vulnerability in PrimeFacesResourceProcessor.java is a perfect example: a legitimate feature (EL-based theme resolution) implemented without the guard that separates intentional EL expressions from arbitrary strings.

The fix is clean, minimal, and behavior-preserving: valid EL expressions still work, plain theme names still work, and the EL engine is no longer invoked on strings that were never meant to be expressions. If you maintain JSF applications, audit every call to createValueExpression() and evaluateExpressionGet() in your codebase — ask where each string argument comes from, and add explicit intent checks where the answer isn't "a string literal in my own code."


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2704

Related Articles

high

CVE-2026-4800: lodash Template Imports Allow Code Execution

CVE-2026-4800 affects lodash's `_.template()` templating API, where untrusted input reaching the `imports` option can lead to arbitrary code execution. The fix was shipped as a dependency upgrade from lodash 4.17.21 to 4.18.1 in the project's lockfile, though the PR itself notes it was never verified against the actual code paths in this repository.

critical

proxy-addr 2.0.7 IP Spoofing: CVE-2026-90711 Trust Bypass

A critical vulnerability in proxy-addr 2.0.7 allowed attackers to spoof client IP addresses by manipulating X-Forwarded-For headers when the trust chain evaluation contained specific misconfigurations. The fix in version 2.0.8 hardens the trust evaluation logic to prevent IP address falsification in Express.js applications relying on this common middleware dependency.

high

TweenMax `_applyCycle` Prototype Pollution via vars.cycle Keys

A bundled copy of the TweenMax animation library copied attacker-influenceable `vars.cycle` property names straight onto a tween configuration object using an unguarded `for...in` loop, so a key named `__proto__`, `constructor`, or `prototype` was written through to the object's prototype chain. The fix adds an explicit key denylist to both copies of the `_applyCycle` helper so those three names are skipped during the merge. No CVE or GHSA is assigned; the issue is tracked as CWE-1321 (Improperl

high

picomatch 2.3.1 ReDoS: Extglob Pattern Catastrophic Backtracking

picomatch versions below 2.3.2, 3.0.2, or 4.0.4 contain a Regular Expression Denial of Service vulnerability in extglob pattern parsing. An attacker can cause catastrophic backtracking with patterns containing nested alternations and quantifiers, freezing any Node.js process that evaluates untrusted glob expressions.

critical

pet-window.js Dynamic Code Evaluation: CWE-94 Hardening via Number

The pet-window module constructed dynamic JavaScript by embedding raw configuration values into code strings. An attacker with local access could inject arbitrary JavaScript by modifying stored configuration. The fix replaces string interpolation with explicit Number() coercion and NaN validation for all numeric configuration parameters.

high

sanitizeUnicodeInput(): Fullwidth U+ Bypasses Codepoint Validation

The `sanitizeUnicodeInput()` helper used by the project character-range settings screen rewrote `U+` prefixes to `0x` and called `parseInt()`, but never normalized its argument first. Compatibility-equivalent forms such as fullwidth `U+`, superscript digits, or mathematical alphanumerics never matched the `/U\+/gi` regex, fell through to the `else return inputString` branch, and were handed back to callers verbatim as "sanitized" values. The fix inserts a `String.prototype.normalize('NFKC')` pas