因为之前微信支付的 XXE 注入安全问题,周末帮别人修这个问题,做了一下笔记。因为使用的是 dom4j,所以为了兼容之前的库,做了简单修改。

代码中 Section 1 是原始代码,Section 2 是修复后的代码,injectXML 是用于测试的代码,测试代码注入的部分是读取 /etc/hosts 文件,注意 methodname 节点,&xxe 引用,在测试 Section 2 的时候,去除引用。


import java.io.StringReader;
import java.util.List;
import java.util.SortedMap;
import java.util.StringTokenizer;
import java.util.TreeMap;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class XXEInject {

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

		String injectXML = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
				"<!DOCTYPE xdsec [\n" +
				"    <!ELEMENT methodname ANY>\n" +
				"    <!ENTITY xxe SYSTEM \"file:///etc/hosts\">\n" +
				"]>\n" +
				"<methodcall>\n" +
				"<methodname>&xxe;</methodname>\n" +
				"</methodcall>";

		/**
		 * unsafe
		 * Section 1
		 */
//		Document document = DocumentHelper.parseText(injectXML);
		/**
		 * Section 1 end
		 */

		/**
		 * safe
		 * Section 2
		 */
		// 修复 XXE 注入
		SAXReader reader = new SAXReader();

		try {
			reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
		} catch (SAXException e1) {
			throw new DocumentException(e1.getMessage());
		}

		String encoding = getEncoding(injectXML);

		InputSource source = new InputSource(new StringReader(injectXML));
		source.setEncoding(encoding);

		Document document = reader.read(source);
		/**
		 * Section 2 end
		 */

		Element root = document.getRootElement();
		List<Element> list = root.elements();

		SortedMap<String, String> map = null;
		if(list!=null && list.size()>0){
			map = new TreeMap<String, String>();
			for(Element e : list){
				String k = e.getName();
				String v = e.getText();
				System.out.println(k+" : "+v);
				map.put(k, v);
			}
		}
	}

	/**
	 * 处理 XXE 注入,方法来自 dom4j
	 * @param text
	 * @return String
	 */
	private static String getEncoding(String text) {
		String result = null;
		String xml = text.trim();
		if (xml.startsWith("<?xml")) {
			int end = xml.indexOf("?>");
			String sub = xml.substring(0, end);
			StringTokenizer tokens = new StringTokenizer(sub, " =\"'");
			while (tokens.hasMoreTokens()) {
				String token = tokens.nextToken();
				if ("encoding".equals(token)) {
					if (!tokens.hasMoreTokens()) {
						break;
					}
					result = tokens.nextToken();
					break;
				}
			}
		}
		return result;

	}

}