一、漏洞描述

微信在JAVA版本的SDK中提供callback回调功能,用来帮助商家接收异步付款结果,该接口接受XML格式的数据,攻击者可以构造恶意的回调数据(XML格式)来窃取商家服务器上的任何文件,一般支付服务器均为核心服务器,出现XXE导致任意文件。另外,一旦攻击者获得了关键支付的安全密钥(md5-key和商家信息,将可以直接实现0元支付购买任何商品)。

二、漏洞来源

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

https://xz.aliyun.com/t/2426?from=groupmessage&isappinstalled=0

三、漏洞分析

漏洞代码

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

微信SDK的xmlToMap方法接收并处理XML数据,但是默认支持外部实体解析,所以只要可以控制strXML那么这边就存在XXE漏洞。

根据README可以看到,微信SDK的支付逻辑如下:
1、首先进行统一下单

import com.github.wxpay.sdk.WXPay;
import java.util.HashMap;
import java.util.Map;
public class WXPayExample {

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

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

        Map<String, String> data = new HashMap<String, String>();
        data.put("body", "腾讯充值中心-QQ会员充值");
        data.put("out_trade_no", "2016090910595900000012");
        data.put("device_info", "");
        data.put("fee_type", "CNY");
        data.put("total_fee", "1");
        data.put("spbill_create_ip", "123.12.12.123");
        data.put("notify_url", "http://www.example.com/wxpay/notify");
        data.put("trade_type", "NATIVE");  // 此处指定为扫码支付
        data.put("product_id", "12");
        try {
            Map<String, String> resp = wxpay.unifiedOrder(data);
            System.out.println(resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

其中notify_url是通知地址,即接入方自己构建的web接口,用于异步接收微信支付结果通知的回调地址。

2、处理微信回调

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

String notifyData这边实际上就是微信给用户的接口返回的数据。

通过流程梳理可以看出,攻击点在于用户的回调接口notify_url,攻击者只需要往notify_url发送精心构造的payload就可以进行XXE攻击,另外,通过README中示例代码可以看出,签名校验是在xmlToMap之后的,所以无需完成签名校验即可完成攻击。

综上,只要使用了漏洞版本SDK&notify_url泄露就可以被XXE攻击

四、漏洞修复

禁用外部实体解析

源链接

Hacking more

...