跳转到帖子

游客您好,欢迎来到黑客世界论坛!您可以在这里进行注册。

赤队小组-代号1949(原CHT攻防小组)在这个瞬息万变的网络时代,我们保持初心,创造最好的社区来共同交流网络技术。您可以在论坛获取黑客攻防技巧与知识,您也可以加入我们的Telegram交流群 共同实时探讨交流。论坛禁止各种广告,请注册用户查看我们的使用与隐私策略,谢谢您的配合。小组成员可以获取论坛隐藏内容!

TheHackerWorld官方

一次曲折的JDK反序列化到JNDI注入绕过(no forceString)

精选回复

发布于

## JDK原生反序列化漏洞的发现和绕过过程

最近的一个渗透项目中,通过nmap扫描了一个Jetty服务。

使用dirsearch扫描路径`/metrics/`,但是之后就找不到了。

从客户提供的Windows账户使用RDP登录,找到开放该端口的服务,使用Everything找到一个zip安装包,拖回来进行安装分析。

安装时,我注意到有一个设置代理的选项。将其设置为我打嗝的地址,并等待稍后可能发生的惊喜。

安装完成后,为其添加调试参数,然后将依赖的jar包导入到IDEA中进行调试。选择一些感兴趣的调试点开始调试。

### 1.通过设置代理,发现反序列化端点(从客户端)

起初,我主要想找到`/metrics/` 背后的内容,所以我尝试了随机GET 和POST 请求。9c32ce5818e229f758556a23730b5b2f.png

请求响应完成后,并没有结束。我在burp中看到这样的记录。a4ee395799108f3b1e5c43df5054172d.png

序列化对象通过http 请求在网络上传输。

于是我先将POST body直接设置为ysoserial/Urldns.jar(检测小工具)的payload,但是dnslog中没有任何响应。我想如果反序列化成功的话,至少应该有检测Windows/Linux的记录。可以看到发送的检测payload没有反序列成功。

同时,我注意到burp的响应中有一个看起来像Exception的stackTrace。

(但是,屏幕截图显示了命令行输入错误的情况)346aff9e873620d24a16122365875d43.png

通过在客户端代码中设置适当的断点,找到发起HTTP请求的地方,复制下来,用Java代码构造请求。

### 2.观察请求/响应,设置正确的数据格式,输出服务器异常

由于当时我已经有了客户端依赖的jar包,所以我在IDEA中搜索了返回的响应类:`ResponseMessage`。61ca822c6529bdfc7d2de681d5ffdb7e.png

在客户端找到这个类。所以readObject恢复响应对象,然后取出里面的Exception对象并打印stackTrace。

````

ResponseMessage 响应消息=readResponse(connection.getInputStream());

System.out.println(responseMessage);

responseMessage.getException().printStackTrace();

````

通过分析异常栈我们知道,对于客户端请求的序列化数据,服务端并不是直接将其反序列化为对象,而是先读取一个Integer类型,然后根据这个Integer值读取后续这个大小的字节,然后进行反序列化。79d600e27c2c3799c4ee8173e3532ef5.png

所以我在发送之前修改了它。c3ba4d2ac415f3b50c59a607c2c829ee.png

### 3.反序列化小工具检测

最近正好在学习Jackson + Spring-aop这个小工具,所以就干脆先试试这个。庆幸的是,这次终于成功进入了反序列化利用流程。79a067cb222670e80287d9d53f62ca7d.png

虽然服务器没有spring-aop依赖。

此处不使用Urldns.jar 进行小工具检测。既然我们已经获得了客户端的依赖jar,那么我们就可以先分析一下这里有什么好东西。或许服务器端也是类似这样的。4ec31b88d0659d78cc10fc040db841a2.png

经过分析,我们得到这样的结果:

- jython(没有此依赖项)

- Commons-Collections(版本是系列2,已修复,关键类不再可序列化)

- Groovy(版本2.4.21,已修复)

- commons-fileupload(版本1.3.3,已修复)

- Commons-Beanutils(没有1 个系列,只有2 个)

注意,根据客户端的推测,服务器端使用的是commons-beanutils2。

https://github.com/melloware/commons-beanutils2

奇怪我以前没见过,github上0星。但快速浏览了一下,发现关键的小工具仍然存在,只是包名变了。

修改包名,在本地测试,发现可以用。bfa14dd1ffd3d593aedfa40955e227c6.png

构建payload并发送给服务器后,报了这样的错误:

````

java.lang.UnsupportedOperationException: 启用Java 安全性时,将禁用对反序列化TemplatesImpl 的支持。这可以通过将jdk.xml.enableTemplatesImplDeserialization 系统属性设置为true 来覆盖。

在java.xml/com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.readObject(TemplatesImpl.java:270)

``af025886b89585362a07dc90a0f4cf61.png

当我看到这个时,我只想将这个属性`jdk.xml.enableTemplatesImplDeserialization`覆盖为true。后来发现这其实是因为SecurityManager开启了。参考[本文](https://c0d3p1ut0s.github.io/%E6%94%BB%E5%87%BBJava%E6%B2%99%E7%AE%B1/)并尝试了各种绕过但失败。其策略是比较严格的。

### 4.从反序列化到JNDI注入

分析不依赖Commons-Collections 的CommonsBeanutils2 小工具。

````

java.util.PriorityQueue#readObject

.

java.util.Comparator#compare

org.apache.commons.beanutils2.BeanComparator#compare

org.apache.commons.beanutils2.PropertyUtils#getProperty

.

Xxx#getYyy()(条件:可序列化,使用空参数的getter方法)

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getOutputProperties(现有)

````

TemplatesImpl这里使用的getOutputProperties是使用最广泛的getter,因为它存在于jdk中,并且可以自定义任何类,并且可以通过各种方式进行扩展(内存马等),但这条路目前被堵住了。您必须更改为单独的getter。我忘记在哪里看到的了。据说在各种dataSource中getConnection()然后使用jdbc url是很方便的。但经检测,服务器没有postgresql、mysql、h2等数据库驱动,只有Oracle。所以我放弃了jdbc url路径。

最后找到了`oracle.jdbc.rowset.OracleCachedRowSet#getConnection`,可以通过getter方法将反序列化转为JNDI注入。ab69c07f35ef382e73fe390a02aaccfb.png

从之前的报错中我们已经知道服务器使用了更高版本的jdk,所以我们要思考如何绕过JNDI注入限制。

绕过限制的方法主要有两种:

- 1.使用反序列化

- 2.使用本地javax.naming.spi.ObjectFactory中的javax.naming.Reference来携带payload

但不考虑反序列化,否则JNDI注入将无法转移。

对象工厂?看看目标环境有什么?首先想到的当然是Tomcat自带的著名的`forceString`属性,依赖于`org.apache.naming.factory.BeanFactory`中的Reference。只需要找到一个接受单个String类型参数的方法就可以完成RCE。方法名称没有限制。千兰大师总结了很多(ELProcessor、Groovy等)。如果你很有钱的话就不用担心这个问题。您需要担心的是Tomcat版本是否在范围内。因为Tomcat7没有添加这个功能,而更高版本的Tomcat(9.0.63、8.5.79)则删除了这个功能。

[这里](https://bz.apache.org/bugzilla/show_bug.cgi?id=65736)讨论了删除`forceString` 函数的官方讨论,

[这里](https://github.com/apache/tomcat/blob/9.0.63/java/org/apache/naming/factory/BeanFactory.java)可以看到9.0.63确实删除了forceString功能。4ce488d2a8056e902cfa6b55bf1a3943.png

不放弃吗?只要在本地尝试一下就知道了。63bdfb08410cd90866db71b3528638d8.png

服务器版本是什么?

直接发个包,让服务器报错:5b45ab10b4ee285df241d84f187fde93.png

9.0.64是修复版本。

因此,forceString 的简单路径不再可用。

可用选项:

- 1.(反序列化getter)从反序列化getter转来的。既然不行,那试试其他的getter方法怎么样?

- 2.(JNDI注入setter)这条路被堵住了,因为像xyz(String Payload)这样的方法不能使用,但是setAbc(String Payload)有没有好的恶意方法呢?

### 5. 没有forceString:反序列化getter/JNDI 注入setter?

通过研究2022年北京网络安全大会上@青蓝的《探索JNDI攻击》,我知道虽然forceString不能用于执行RCE,但仍然有其他方法可以执行敏感操作。1ffd66148c3d8c5c679d30985df53836.png

5e97503e65929fae27e80027f86abf8b.png

他在ppt中介绍了使用commons-configuration(2)/groovy + tomcat-jdbc.jar实现System.setProperty()的效果。

让我明白一下,这里的原理是首先找到一个特殊的ObjectFactory:`org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory`(tomcat-jdbc.jar)。与org.apache.naming.factory.BeanFactory(catalina.jar)相比,它支持调用类中的所有方法,包括静态方法,只要以set开头即可。并且`org.apache.naming.factory.BeanFactory`根据属性找到对应的setter方法(只有当属性abc存在时才会调用方法setAbc(String value))。如果我的理解有什么错误,请指正。

附加`org.apache.naming.factory.BeanFactory`:db5f1faa8417c291dc79a611f99d11f9.png

`org.apache.tomcat.jdbc.naming.GenericNamingResourcesFactory`:7c9c05647dcdebf6ba20731e6042677c.png

1e2b2be9347d406764e3ed6686ace3db.png

另一个特殊类是“org.apache.commons.configuration2.SystemConfiguration”(commons-configuration2-*.jar) 或“org.apache.commons.configuration.SystemConfiguration”(commons-configuration-*.jar)。

其setSystemProperties方法可以设置系统属性,即

````

系统.setProperty()

````

setSystemProperties方法接收一个名为`fileName`的String类型参数,但实际上最终会构造成一个URL对象,所以不仅可以传入本地文件,还可以传入网络请求,bf647b90b7f45c95e5f2542728100efd.png

这样我们就可以在我们控制的Web服务器上放一个文件,内容是每行一个

````

键=值

````

想想之前TemplatesImpl的失败,不就是因为`jdk.xml.enableTemplatesImplDeserialization`系统属性导致的吗?我在这里发现了一个机会,所以我先改变了这个属性。

我满心欢喜地再次发送了有效负载,却再次收到此错误:a3cfcb58e1fb0804e0cf35af765f57ab.png

嗯,还不错。至少说明设置系统属性的小工具是有效的。

该系统属性已被更改,但仍然无法成功利用。还有其他值得更改的系统属性吗?

### 6. 应用程序启动后,System.setProperty() 能否绕过JNDI 注入限制?

回想千兰大师在ppt中提到的,可以尝试修改这两个系统属性作为JNDI注入缓解措施:

````

com.sun.jndi.ldap.object.trustURLCodebase=true

com.sun.jndi.rmi.object.trustURLCodebase=true

````

继续测试。70ceb63412feaa7f8b79e531863f52e3.png

修改后发现使用JNDIExploit的`/Basic/Command/`依然不成功。于是我尝试在本地搭建环境,看看是否真的能成功。

我先使用Spring环境尝试一下(实际上是java-sec-code):284be831f77248b25ed03cc19295a74b.png

关键点是这个类:`com.sun.naming.internal.VersionHelper` a9de2f860d65bd86c68a1be1d8f2e498.png

在加载Class时,会判断其私有静态最终属性“TRUST_URL_CODE_BASE”的值。只有当它为true 时,才能从远程URL 中拉取该类。当第一次加载此类时,将根据当时的系统属性“com.sun.jndi.ldap.object.trustURLCodebase”分配该值。因此,即使我们稍后在代码中将系统属性设置为true,“TRUST_URL_CODE_BASE”也将不再从系统属性中获取值。所以我们不必急于使用它。

从下图可以看出,spring-boot启动时会加载这个类:284be831f77248b25ed03cc19295a74b.png

起初,我以为只有spring-boot 才会出现这种情况。我们的目标环境是tomcat,实际情况可能不是这样。之后我又在tomcat环境下进行了测试。

发现使用JNDI注入而不绕过`/Basic/Command/`仍然失败。仍然落在这个“TRUST_URL_CODE_BASE”上。fa82ce374320d568521892b1b95ca626.png

为了让我们能够观察到该属性被赋值的时刻,调试参数也设置为`server=y,suspend=y`。028491a83016cede1f25e723013392db.png

调用堆栈是:

````

创建帐户或登录后发表意见

最近浏览 0

  • 没有会员查看此页面。