Java EL and Spel表达式注入漏洞成因和一些细节
字数 2039 2025-08-10 10:14:26
Java EL 和 SpEL 表达式注入漏洞深度解析
1. EL (Expression Language) 基础
1.1 EL 背景与特点
EL (Expression Language) 最初是为了简化 JSP 开发而设计的,主要特点包括:
- 可直接访问 JSP 内置对象 (page, request, session, application)
- 提供丰富的运算符(关系、逻辑、算术等)
- 支持扩展函数(可调用 Java 类的静态方法)
- 简化数据访问方式
1.2 EL 基本语法
EL 表达式语法格式:${expression}
数据存取方式:
- 点号表示法:
${user.name} - 方括号表示法:
${user["name"]}
特殊情况必须使用方括号:
- 属性名称包含特殊字符(如
.、-等非字母数字字符) - 需要动态取值时
1.3 EL 隐式对象
| 对象 | 等效 Java 代码 | 描述 |
|---|---|---|
pageContext |
PageContext |
JSP 页面上下文 |
param |
request.getParameter() |
获取单个请求参数 |
paramValues |
request.getParameterValues() |
获取请求参数数组 |
header |
request.getHeader() |
获取单个请求头 |
headerValues |
request.getHeaders() |
获取请求头数组 |
cookie |
request.getCookies() |
获取 cookie |
1.4 禁用 EL 表达式
单个 JSP 文件禁用:
<%@page isELIgnored="true" %>
全局禁用(web.xml 配置):
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>
2. EL 表达式注入漏洞原理
2.1 漏洞示例代码分析
@RequestMapping("/el")
@ResponseBody
public String ElTest(@RequestParam(required = false) String expression) {
if (expression == null) {
return "Send request with \"expression\" parameter!";
}
try {
ExpressionFactoryImpl factory = new ExpressionFactoryImpl();
StandardELContext context = new StandardELContext(factory);
ValueExpression valueExpression = factory.createValueExpression(context, expression, String.class);
valueExpression.getValue(context);
} catch (Exception e) {
return Util.printStack(e);
}
return "OK!";
}
2.2 表达式解析与执行流程
-
表达式解析:
ExpressionFactoryImpl#createValueExpression创建 ValueExpression 对象ExpressionBuilder#createValueExpression构建表达式build方法将表达式拆分为 AST 节点createNodeInternal方法通过 AST 解析为各种节点类型
-
表达式执行:
ValueExpressionImpl#getValue获取表达式执行结果- 核心调用链:
this.getNode().getValue→AstValue#getValue - 处理流程:
- 获取第一个子节点的值
- 检查是否存在次子节点
- 如果存在
AstMethodParameters节点,获取该节点 - 通过
resolver#invoke方法结合base/suffix/params进行计算
3. SpEL (Spring Expression Language) 表达式注入
3.1 SpEL 与 EL 的区别
-
功能范围:
- SpEL 功能更强大,支持方法调用、类型操作等
- EL 主要用于简化 JSP 中的数据访问
-
上下文:
- SpEL 可用于 Spring 应用的任何地方
- EL 主要用于 JSP/JSF 等视图技术
-
语法:
- SpEL 使用
#{...}语法 - EL 使用
${...}语法
- SpEL 使用
3.2 SpEL 注入漏洞示例
@RequestMapping("/spel")
@ResponseBody
public String SpELTest(@RequestParam String input) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
Expression exp = parser.parseExpression(input);
return exp.getValue(context, String.class);
}
3.3 SpEL 安全防护
-
使用
SimpleEvaluationContext替代StandardEvaluationContext:EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); -
禁用危险功能:
SpelParserConfiguration config = new SpelParserConfiguration( SpelCompilerMode.OFF, this.getClass().getClassLoader() ); ExpressionParser parser = new SpelExpressionParser(config);
4. 表达式注入攻击场景
4.1 常见攻击载荷
EL 注入示例:
${''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc')}
SpEL 注入示例:
T(java.lang.Runtime).getRuntime().exec('calc')
4.2 漏洞利用条件
- 用户输入直接作为表达式被解析
- 使用
StandardEvaluationContext(SpEL) - 未对表达式内容进行过滤或限制
5. 防御措施
5.1 通用防御策略
-
避免直接解析用户输入:
- 不要将不可信输入直接作为表达式解析
-
使用安全上下文:
- SpEL:优先使用
SimpleEvaluationContext - EL:禁用或限制功能
- SpEL:优先使用
-
输入过滤:
- 白名单验证表达式内容
- 过滤危险字符和方法调用
5.2 代码示例
安全的 SpEL 使用:
SpelExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
String safeExpression = "safeProperty"; // 来自可信源
Object value = parser.parseExpression(safeExpression).getValue(context);
安全的 EL 使用:
// 禁用危险功能
ExpressionFactory factory = ExpressionFactory.newInstance();
ELContext context = new StandardELContext(factory);
// 限制可访问的对象和方法
context.addELResolver(new MySafeELResolver());
6. 总结
Java EL 和 SpEL 表达式注入漏洞源于对不可信输入的过度信任和不当解析。理解其工作原理和攻击方式对于构建安全的 Java Web 应用至关重要。开发者应:
- 充分认识表达式语言的潜在风险
- 采用最小权限原则配置表达式解析环境
- 实施严格的输入验证和过滤机制
- 定期进行安全审计和代码审查
通过合理的安全措施,可以在享受表达式语言便利性的同时,有效防范相关安全风险。