CVE-2016-5003 xmlrpc反序列化漏洞

一、xmlrpc简介

Apache XML-RPC是一个java的XML-RPC库。XML-RPC是在XML的帮助下通过HTTP进行远程过程调用的协议。 Apache XML-RPC可以在客户端用于进XML-RPC调用,也可以在服务器端用XML-RPC公开一些函数。现在ws-xmlrpc库不被Apache支持。 最新版本是2013年发布的3.1.3版本。但是,许多应用程序仍然使用ws-xmlrpc库

二、漏洞重现

新建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>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
    </dependencies>

主方法App.java

public class App {  
    private static final int port = 8888;

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

接收传入对象User.java

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

运行App.java后即在8888端口建立HTTP服务。即可使用Burpsuite进行发包测试 参考官网帮助文档,可以发现xmlrpc支持java.io.Serializable序列化后的base64编码的数据,但是需要引用命名空间http://ws.apache.org/xmlrpc/namespaces/extensions。所以使用ysoserialCommonsCollections5生成payload。因为本机测试环境为jdk1.8,用其它payload会爆错 然后使用certutil -encode 将生成的文件进行base64编码。

漏洞分析

当客户端发送请求时,org.apache.xmlrpc.server.XmlRpcStreamServer.getRequest(XmlRpcStreamRequestConfig, InputStream)方法会接收到该请求,该方法会将请求内容封闭到XMLReader对象中。

protected XmlRpcRequest getRequest(final XmlRpcStreamRequestConfig pConfig,  
                                       InputStream pStream) throws XmlRpcException {
        final XmlRpcRequestParser parser = new XmlRpcRequestParser(pConfig, getTypeFactory());
        final XMLReader xr = SAXParsers.newXMLReader();
        xr.setContentHandler(parser);
        try {
            xr.parse(new InputSource(pStream));
        } catch (SAXException e) {
            Exception ex = e.getException();
            if (ex != null  &&  ex instanceof XmlRpcException) {
                throw (XmlRpcException) ex;
            }
            throw new XmlRpcException("Failed to parse XML-RPC request: " + e.getMessage(), e);
        } catch (IOException e) {
            throw new XmlRpcException("Failed to read XML-RPC request: " + e.getMessage(), e);
        }
        final List params = parser.getParams();
        return new XmlRpcRequest(){
            public XmlRpcRequestConfig getConfig() { return pConfig; }
            public String getMethodName() { return parser.getMethodName(); }
            public int getParameterCount() { return params == null ? 0 : params.size(); }
            public Object getParameter(int pIndex) { return params.get(pIndex); }
        };
    }

然后程序会走到org.apache.xmlrpc.parser.XmlRpcRequestParser.endElement(String, String, String)方法来获取各个元素的值

public void endElement(String pURI, String pLocalName, String pQName) throws SAXException {  
        switch(--level) {
            case 0:
                break;
            case 1:
                if (inMethodName) {
                    if ("".equals(pURI)  &&  "methodName".equals(pLocalName)) {
                        if (methodName == null) {
                            methodName = "";
                        }
                    } else {
                        throw new SAXParseException("Expected /methodName, got "
                                                    + new QName(pURI, pLocalName),
                                                    getDocumentLocator());
                    }
                    inMethodName = false;
                } else if (!"".equals(pURI)  ||  !"params".equals(pLocalName)) {
                    throw new SAXParseException("Expected /params, got "
                            + new QName(pURI, pLocalName),
                            getDocumentLocator());
                }
                break;
            case 2:
                if (!"".equals(pURI)  ||  !"param".equals(pLocalName)) {
                    throw new SAXParseException("Expected /param, got "
                                                + new QName(pURI, pLocalName),
                                                getDocumentLocator());
                }
                break;
            case 3:
                if (!"".equals(pURI)  ||  !"value".equals(pLocalName)) {
                    throw new SAXParseException("Expected /value, got "
                                                + new QName(pURI, pLocalName),
                                                getDocumentLocator());
                }
                endValueTag();
                break;
            default:
                super.endElement(pURI, pLocalName, pQName);
                break;
        }
    }

当获取到最后的元素时,会执行org.apache.xmlrpc.parser.RecursiveTypeParserImpl.endValueTag()方法

protected void endValueTag() throws SAXException {  
        if (inValueTag) {
            if (typeParser == null) {
                addResult(text.toString());
                text.setLength(0);
            } else {
                typeParser.endDocument();
                try {
                    addResult(typeParser.getResult());
                } catch (XmlRpcException e) {
                    throw new SAXException(e);
                }
                typeParser = null;
            }
        } else {
            throw new SAXParseException("Invalid state: Not inside value tag.",
                                        getDocumentLocator());
        }
    }

即会执行typeParser.getResult(),并且此时的typeParser为SerializableParser,

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

从而造成了反序列化命令执行漏洞。

zhutougg

继续阅读此作者的更多文章