Description
OGNL (Object-Graph Navigation Language) supports property navigation, method calls, operators, collections and access to objects exposed through an evaluation context. Apache Struts uses OGNL in its value stack and other internal features.
Parsing or evaluating externally controlled text as an OGNL expression may let an attacker access unintended objects or properties. The impact depends on the root object, evaluation context, permitted classes and members, and Struts security settings. It can range from data disclosure or modification to code execution.
The result of parseExpression or compile remains an untrusted expression. Successful parsing does not establish authorization or make later evaluation safe.
Potential impact
- Data disclosure or modification: An expression may read or change properties in the reachable object graph.
- Security-logic bypass: If an expression result controls authorization, routing or policy decisions, an attacker may manipulate those decisions.
- Code execution: Configurations exposing dangerous objects, methods, classes or context access may permit execution with the application’s privileges.
- Denial of service: Complex or repetitive expressions may consume excessive CPU, memory or object-access resources.
Remediation
Keep expression text under application control and pass external input only as data.
- Evaluate only fixed, reviewed expressions. Do not concatenate request values into expression text or forced/double-evaluation syntax.
- If users must select a field or operation, map external keys to constant expressions through a finite server-owned mapping and reject unknown keys.
- Supply untrusted values as root-object data, evaluation-context data, bindings or property values.
- Do not assume a regex, escaping, character removal or a helper named
sanitizecan make arbitrary OGNL expressions safe. Even an alphanumeric-and-underscore restriction can leave an attacker free to choose server-object property names. - Keep Apache Struts on a supported release and use the OGNL dependency managed by Struts. The Struts 7.3.0 and OGNL 3.4.12 links below are version-specific references; check support status and security updates when deploying.
Examples
Before
The before example evaluates the entire request value as an OGNL expression.
import jakarta.servlet.http.HttpServletRequest;
import ognl.Ognl;
import ognl.OgnlException;
final class VulnerableOgnlExample {
static Object readField(HttpServletRequest request, UserProfile profile)
throws OgnlException {
String requestExpression = request.getParameter("expression");
return Ognl.getValue(requestExpression, profile);
}
static final class UserProfile {
private final String displayName;
private final String email;
UserProfile(String displayName, String email) {
this.displayName = displayName;
this.email = email;
}
public String getDisplayName() {
return displayName;
}
public String getEmail() {
return email;
}
}
}
requestExpression may contain method calls, context references or other OGNL syntax rather than a simple property name. The attacker chooses the expression itself.
After
The after example treats input as a limited business key and maps it to a server-owned constant expression. It requires a Java version supporting switch expressions. Configure access to the profile and its fields, and error responses, separately.
import jakarta.servlet.http.HttpServletRequest;
import ognl.Ognl;
import ognl.OgnlException;
final class SafeOgnlExample {
static Object readField(HttpServletRequest request, UserProfile profile)
throws OgnlException {
String requestedField = request.getParameter("field");
if (requestedField == null) {
throw new IllegalArgumentException("Missing field");
}
String fixedExpression = switch (requestedField) {
case "display-name" -> "displayName";
case "email" -> "email";
default -> throw new IllegalArgumentException("Unsupported field");
};
return Ognl.getValue(fixedExpression, profile);
}
static final class UserProfile {
private final String displayName;
private final String email;
UserProfile(String displayName, String email) {
this.displayName = displayName;
this.email = email;
}
public String getDisplayName() {
return displayName;
}
public String getEmail() {
return email;
}
}
}
Accepts only the two business keys. The string passed to OGNL is always the literal displayName or email defined in code.
Apache Struts defense in depth
These controls supplement fixed-expression design; they do not neutralize attacker-selected expressions.
- Keep the class/package allow-list
struts.allowlist.enableenabled and allow only necessary entries. It is enabled by default in Struts 7. - Configure OGNL Guard through
struts.ognl.excludedNodeTypesto block unnecessary AST node types. - Where possible, disable fallback access to ActionContext with
struts.ognl.valueStackFallbackToContext=false. - Do not relax Struts 7 restrictions such as
struts.ognl.allowStaticFieldAccess=false,struts.disallowProxyObjectAccess=true,struts.disallowDefaultPackageAccess=trueandstruts.ognl.disallowCustomOgnlMap=true. - Keep
struts.ognl.expressionMaxLengthas low as the application permits. A length limit reduces complexity but does not validate expression meaning.
Apache Struts documents that the -Dognl.security.manager sandbox does not work on JDK 21 or later. Do not rely on it as a security boundary or present it as a solution for current JDKs.
Review expression use
Keep expressions separate from data in custom helpers and templates that evaluate strings later. Review authorization-related results and which root-object data is exposed.
References
- Apache Struts security guide
- Apache Struts 7.3.0 release notes
- Apache Struts releases
- Apache Struts API documentation
- OGNL 3.4.12 release
- OGNL language guide
- CWE-917: Improper Neutralization of Special Elements used in an Expression Language Statement
- OWASP Top 10:2025 A05 Injection
- OWASP Top 10:2021 A03 Injection
- OWASP ASVS 5.0.0