0x01 前言
之前分析了下Jenkins Nested View插件XXE漏洞(CVE-2021-21680),就想着总结下Java的XXE。
Google了下发现有前辈已经总结好了,所以就照着做下实验。
0x02 XXE漏洞基础
XXE漏洞原理:
XXE全称XML External Entity,XML外部实体注入。通过在XML中声明DTD外部实体,并在XML中引入,便可以获取我们想要的数据。
XML文档结构:
XML主要由7个部分组成:文档声明、标签/元素、属性、注释、实体字符、CDATA 字符数据区、处理指令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?xml version="1.0" encoding="ISO-8859-1"?>
<bookstore> <book category="COOKING"> <title lang="en">Everyday Italian</title> <author>Giada De Laurentiis<</author> <year>2005</year> <price>30.00</price> <script> <![CDATA[ function matchwo(a,b) { if (a < b && a < 0) then {return 1;} else {return 0;} } ]]> </script> </book> </bookstore>
|
CDATA 指的是不应由 XML 解析器进行解析的文本数据(Unparsed Character Data)。CDATA 部分中的所有内容都会被解析器忽略。
CDATA 部分由 “**” 结束,某些文本比如 JavaScript 代码,包含大量 “<” 或 “&” 字符。为了避免错误,可以将脚本代码定义为 CDATA。
XML示例:
(1)如下实例,<?xml version="1.0" encoding="ISO-8859-1"?>
是XML声明,<!DOCTYPE description [<!ENTITY abc "123213123123123" >]>
就是DTD文档类型定义,<description>&abc;</description>
是文档元素 其中abc就是引用的dtd中的abc。
1 2 3 4 5
| <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE description [ <!ENTITY abc "123213123123123" > ]> <description>&abc;</description>
|
DTD文档类型定义:
DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。DTD 可以在 XML 文档内声明,也可以从外部引用。DTD实体也分为外部引用和内部定义。
DTD两种引入方式:
在XML内部声明DTD:
1 2 3 4 5 6 7 8 9 10 11 12
| <?xml version="1.0"?> <!DOCTYPE note [ <!ELEMENT note (to,from,heading,body)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT message (#PCDATA)> ]> <note> <to>George</to> <from>John</from> <message>Reminder</message> </note>
|
在XML中引入外部DTD文档
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?xml version="1.0"?> <!DOCTYPE note SYSTEM "note.dtd"> <note> <to>George</to> <from>John</from> <message>Reminder</message> </note>
<!ELEMENT note (to,from,message)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT message (#PCDATA)>
|
DTD实体两种方式:
实体又分为一般实体和参数实体:一般实体的声明语法&实体名;
;参数实体只能在DTD中使用,参数实体的声明格式:%实体名;
。
内部实体:
在dtd中定义了一个名为test的实体,实体的值为test-value:
1
| <!ENTITY test "test-value">
|
外部实体:
在dtd中定义了一个名为test的实体,实体的值为URL:
1 2 3
| <!ENTITY test SYSTEM "URI/URL"> <!ENTITY test SYSTEM "file:///C://1.dtd"> <!ENTITY test SYSTEM "http://test.com/1.dtd">
|
常见的xml解析方式有以下四种:
1)DOM生成和解析XML文档
2)SAX生成和解析XML文档
3)DOM4J生成和解析XML文档
4)JDOM生成和解析DOM文档
SAX是以流的形式读取XML文档中的内容,并在读取过程中自动调用预先定义的处理方法;DOM是将整个xml文档中的内容以tree的形式存储在内存当中,可以对这个tree中的任意一个节点进行操作,SAX则不能。SAX不适合用来修改xml内容,DOM需要耗费大量内存
0x03 实验
在Java中有很多jar包可以来解析XML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| javax.xml.parsers.DocumentBuilder javax.xml.parsers.SAXParser javax.xml.parsers.SAXParserFactory javax.xml.transform.TransformerFactory javax.xml.validation.Validator javax.xml.validation.SchemaFactory javax.xml.transform.sax.SAXTransformerFactory javax.xml.transform.sax.SAXSource org.xml.sax.XMLReader org.xml.sax.helpers.XMLReaderFactory org.dom4j.io.SAXReader org.jdom.input.SAXBuilder org.jdom2.input.SAXBuilder javax.xml.bind.Unmarshaller javax.xml.xpath.XpathExpression javax.xml.stream.XMLStreamReader org.apache.commons.digester3.Digester
|
笔者本次通过web servlet实现引用以上部分jar包解析xml来进行实验,并补充其中的修复方案。本次实验借鉴了前辈总结的代码。
实验1 SAXBuilder解析xml并修复漏洞
实验编写的servlet如下,其中postNoFixXxe是未修复时的方法,postWithFixXxe是修复后的方法,修复时通过 builder.setFeature设置外部实体不能方法从而防御xxe漏洞。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter;
import org.jdom2.JDOMException; import org.jdom2.input.SAXBuilder; import org.jdom2.Document; import org.jdom2.output.XMLOutputter;
public class SAXBuilderServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { postWithFixXxe(req, resp);
}
private void postNoFixXxe(HttpServletRequest req, HttpServletResponse resp) { try { BufferedReader reader = req.getReader(); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(reader); PrintWriter writer = resp.getWriter(); XMLOutputter outputter = new XMLOutputter(); outputter.output(doc, writer);
} catch (JDOMException | IOException e) { e.printStackTrace(); } }
private void postWithFixXxe(HttpServletRequest req, HttpServletResponse resp) { try { BufferedReader reader = req.getReader(); SAXBuilder builder = new SAXBuilder(); builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); builder.setFeature("http://xml.org/sax/features/external-general-entities", false); builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false); builder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); Document doc = builder.build(reader); PrintWriter writer = resp.getWriter(); XMLOutputter outputter = new XMLOutputter(); outputter.output(doc, writer);
} catch (JDOMException | IOException e) { e.printStackTrace(); } }
}
|
当在doPost中调用postNoFixXxe时,构造xml,成功访问文件内容,可以导致xxe漏洞。
当在doPost中调用postWithFixXxe时,成功防御了xxe漏洞,响应未输出xml,并且log报错不允许dtd。
实验2 DocumentBuilder(原生dom解析xml)
为了方便输出用了TransformerFactory将document输出到响应流。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| import org.w3c.dom.Document; import org.xml.sax.InputSource;
import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintWriter;
public class DocumentBuilderServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { postNoFixXxe(req, resp);
}
private void postNoFixXxe(HttpServletRequest req, HttpServletResponse resp) { try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); BufferedReader br = new BufferedReader(req.getReader()); Document document = documentBuilder.parse(new InputSource(br)); PrintWriter writer = resp.getWriter(); String textContent = document.getDocumentElement().getTextContent(); writer.print(textContent); } catch (Exception e) { e.printStackTrace(); }
}
private void postWithFixXxe(HttpServletRequest req, HttpServletResponse resp) { try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); documentBuilderFactory.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true); documentBuilderFactory.setExpandEntityReferences(false); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); BufferedReader br = new BufferedReader(req.getReader()); Document document = documentBuilder.parse(new InputSource(br));
PrintWriter writer = resp.getWriter(); String textContent = document.getDocumentElement().getTextContent(); writer.print(textContent); } catch (Exception e) { e.printStackTrace(); }
} }
|
调用postNoFixXxe
调用postWithFixXxe
实验3 SAXReader
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;
import org.dom4j.Document; import org.dom4j.io.SAXReader; import org.dom4j.io.XMLWriter;
public class SAXReaderServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { postNoFixXxe(req,resp);
}
private void postNoFixXxe(HttpServletRequest req, HttpServletResponse resp){ try{ SAXReader saxReader = new SAXReader(); Document doc = saxReader.read(req.getReader()); XMLWriter xmlWriter = new XMLWriter(resp.getWriter()); xmlWriter.write(doc); }catch (Exception e){ e.printStackTrace(); }
}
private void postWithFixXxe(HttpServletRequest req, HttpServletResponse resp){ try{ 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.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); Document doc = saxReader.read(req.getReader()); XMLWriter xmlWriter = new XMLWriter(resp.getWriter()); xmlWriter.write(doc); }catch (Exception e){ e.printStackTrace(); }
} }
|
调用postNoFixXxe
调用postWithFixXxe
SAXTransformerFactory解析xml,需要xslt,否则会报错,所以构造时要构造xslt。正常情况需要将xml输出,本次实验为了节省时间没有输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.XMLConstants; import javax.xml.transform.Result; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import java.io.IOException;
public class SAXTransformerFactoryServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { postNoFixXxe(req, resp);
}
private void postNoFixXxe(HttpServletRequest req, HttpServletResponse resp){ try{ SAXTransformerFactory sf = (SAXTransformerFactory) SAXTransformerFactory.newInstance(); StreamSource source = new StreamSource(req.getReader()); TransformerHandler transformerHandler = sf.newTransformerHandler(source); Result result = new StreamResult(resp.getWriter()); transformerHandler.setResult(result); }catch (Exception e){ e.printStackTrace(); }
}
private void postWithFixXxe(HttpServletRequest req, HttpServletResponse resp){ try{ SAXTransformerFactory sf = (SAXTransformerFactory) SAXTransformerFactory.newInstance(); sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); StreamSource source = new StreamSource(req.getReader()); TransformerHandler transformerHandler = sf.newTransformerHandler(source); Result result = new StreamResult(resp.getWriter()); transformerHandler.setResult(result); }catch (Exception e){ e.printStackTrace(); }
} }
|
构造xml和xslt如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE xxe [ <!ENTITY file SYSTEM "file:///E://111.txt" > ]> <?xml-stylesheet type="text/xsl" href="E://222.xsl"?> <catalog> <test>&file;</test> </catalog>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/"> <html> <body> <h2>teat</h2> <table border="1"> <tr bgcolor="#9acd32"> <th align="left">File</th> </tr> <xsl:for-each select="catalog"> <tr> <td><xsl:value-of select="test"/></td> </tr> </xsl:for-each> </table> </body> </html> </xsl:template>
</xsl:stylesheet>
|
构造xml请求,在未禁止外部实体情况下可以正常响应,控制台未报错:
修复后报错:
这个就是Jenkins Nested View插件XXE漏洞(CVE-2021-21680)用到的xml解析工具。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.XMLConstants; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import java.io.IOException;
public class TransformerFactoryServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { postNoFixXxe(req, resp);
}
private void postNoFixXxe(HttpServletRequest req, HttpServletResponse resp){
try { TransformerFactory tf = TransformerFactory.newInstance(); StreamSource source = new StreamSource(req.getReader()); tf.newTransformer().transform(source, new StreamResult(resp.getWriter())); } catch (TransformerException | IOException e) { e.printStackTrace(); }
}
private void postWithFixXxe(HttpServletRequest req, HttpServletResponse resp){
try { TransformerFactory tf = TransformerFactory.newInstance(); StreamSource source = new StreamSource(req.getReader()); tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); tf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); tf.newTransformer().transform(source, new StreamResult(resp.getWriter())); } catch (TransformerException | IOException e) { e.printStackTrace(); }
} }
|
postNoFixXxe:
postWithFixXxe:
Unmarshaller
使用默认的解析方法不会存在XXE问题,这也是唯一一个使用默认的解析方法不会存在XXE的一个库。
1 2 3 4 5
| Class tClass = Some.class; JAXBContext context = JAXBContext.newInstance(tClass); Unmarshaller um = context.createUnmarshaller(); Object o = um.unmarshal(ResourceUtils.getPoc1()); tClass.cast(o);
|
小结:
基本上所有的修复都是设置禁止外部实体,但是需要注意通过setFeature或者setAttribute或者setProperty设置属性时,一定要注意在解析xml之前就设置,否则修复也是无效的。
0x04 危害
xxe漏洞危害:
1、读取系统文件,信息泄露
1 2 3 4 5
| <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE xxe [ <!ENTITY file SYSTEM "file:///E://111.txt" > ]> <xxe>&file;</xxe>
|
2、Blind XXE 可以实施OoB数据外带攻击:https://www.cnblogs.com/3ichae1/p/13140229.html
3、探测内网端口-SSRF
1 2 3 4 5
| <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE xxe [ <!ENTITY url SYSTEM "http://192.168.116.1:90/" > ]> <xxe>&url;</xxe>
|
4、执行系统命令
1 2 3 4 5
| <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE xxe [ <!ENTITY url SYSTEM "expect://id" > ]> <xxe>&url;</xxe>
|
5、作为中间隧道,通过请求间接攻击内网网站
1 2 3 4 5
| <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE xxe [ <!ENTITY url SYSTEM "带payload的url可getshell" > ]> <xxe>&url;</xxe>
|
6、拓展,不同语言支持的协议:
libxml2 |
PHP |
Java |
.NET |
file http ftp |
file http ftp php compress.zlib compress.bzlip2 data glob phar |
file http https ftp jar netdoc mailto gopher |
file http https ftp
|
0x05 总结
Java XXE漏洞的修复或预防主要在设置禁止dtd,一般出现漏洞需要注意:
- 是否禁止dtd或者entity
- 参数是否可控
- 传入参数格式为REST XML格式,X-RequestEntity-ContentType: application/xml
0x06 参考链接:
https://blog.spoock.com/2018/10/23/java-xxe/
https://www.leadroyal.cn/p/562/
https://www.cnblogs.com/r00tuser/p/7255939.html
https://www.runoob.com/dtd/dtd-entities.html
https://www.cnblogs.com/avivahe/p/5701392.html
https://stackoverflow.com/questions/27985355/outputting-xml-on-java-servlet-using-printwriter
https://security.tencent.com/index.php/blog/msg/69