某个风和日丽的下午,突然收到领导的微信截图,
看到后虎躯一震,没遇到过o(╯□╰)o!!!!平复后使用无所不知的度娘XXE漏洞详情
XML外部实体注入漏洞(XML External Entity Injection,简称 XXE),以下内容需要xml基础,再次不再科普。
众所周知,我们服务端获取微信支付回调结果的通知是通过读取流并转换成xml的形式,
/** 支付成功后,微信回调返回的信息 */
String result = new String(outSteam.toByteArray(), "utf-8");
Map<Object, Object> map = XMLUtil.doXMLParse(result);
但此时如果别人获取了我们的回调地址,并仿造了微信的返回结果,那么他是可以获取到我们服务器的信息,如这样
或者这样
或者这样
那么如何解决呢,xxe说到底就是非法的xml,那么我们是可以在解析xml解析时,加入校验代码如下
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();
}
附上解析和校验的完整代码
/** 支付成功后,微信回调返回的信息 */
String result = new String(outSteam.toByteArray(), "utf-8");
Map<Object, Object> map = XMLUtil.doXMLParse(result);
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static Map doXMLParse(String strxml) throws JDOMException,IOException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
Map<String, String> data = new HashMap<String, String>();
if(null == strxml || "".equals(strxml)) {
return data;
}
try {
DocumentBuilder documentBuilder = WXPayXmlUtil.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) {
logger.error(" 微信支付回调 xml 解析失败 ", ex);
}
return data;
} catch (Exception ex) {
WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strxml);
logger.error(" 微信支付回调 xml 解析失败 ", ex);
return data;
}
}
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();
}
public static Document newDocument() throws ParserConfigurationException {
return newDocumentBuilder().newDocument();
}
}
PS:严重吐槽下,修复了漏洞,但是登录微信商户平台后,微信仍然提示修复漏洞,不修复关闭微信支付权限云云,和微信客服沟通,回馈,不论有没有修复,都会提示修复漏洞,严重鄙视!
附上官方链接https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=23_5