红日代审Day3-实例化任意对象漏洞
class_exists
1 | class_exists(string $class, bool $autoload = true): bool |
class类名。名称以不区分大小写的方式匹配。
autoload如果尚未加载,是否自动加载。
返回值
如果
class是已经定义的类,则返回 **true**,否则返回 **false**。
demo:
1 |
|
SimpleXMLElement
SimpleXMLElement 是 PHP 中的一个内置类,用于解析和操作 XML 数据。它是一个非常轻量级且直观的工具,可以将 XML 数据解析为对象形式,允许你以类似对象的方式访问和修改 XML 元素和属性
类摘要:
1 | class SimpleXMLElement implements Stringable, Countable, RecursiveIterator { |
Demo分析
这段代码中存在两个安全漏洞。第一个是文件包含漏洞,上图第8行中使用了 class_exists() 函数来判断用户传过来的控制器是否存在,默认情况下,如果程序存在 __autoload 函数,那么在使用 class_exists() 函数就会自动调用本程序中的 __autoload 函数,这题的文件包含漏洞就出现在这个地方。攻击者可以使用 路径穿越 来包含任意文件,当然使用路径穿越符号的前提是 PHP5~5.3(包含5.3版本)版本 之间才可以。例如类名为: ../../../../etc/passwd 的查找,将查看passwd文件内容
即**$controllerName=$_GET[‘c’];可控,随后传入 class_exists() 函数,此时会调用__autoload** 函数触发**include $className;**造成文件包含,于是就可以通过路径穿越包含任意文件。
而第二个漏洞在于上图第9行,我们发现实例化类的类名和传入类的参数均在用户的控制之下。攻击者可以通过该漏洞,调用PHP代码库的任意构造函数。即使代码本身不包含易受攻击的构造函数,我们也可以使用PHP的内置类 SimpleXMLElement 来进行 XXE 攻击,进而读取目标文件的内容,甚至命令执行(前提是安装了PHP拓展插件expect)
关于 SimpleXMLElement 导致的XXE攻击,下面再给出一个demo案例,方便大家理解:
这里SimpleXMLElement实例第一个参数是我们要解析的XML字符串,而第二个参数LIBXML_NOENT是漏洞的关键,即启用实体替换,这样就能够加载外部实体造成XXE漏洞
实例分析
审计
环境:shopware 5.3.3
该cms漏洞在engine\Shopware\Controllers\Backend\ProductStream.php 文件中有一个 loadPreviewAction 方法,其作用是用来预览产品流的详细信息,具体代码如下:
该方法接收从用户传来的参数 sort ,然后传入 Repository 类的 unserialize 方法(如上图第11-14行代码),我们跟进 Repository 类,查看 unserialize 方法的实现。该方法我们可以在 engine\Shopware\Components\ProductStream\Repository.php 文件中找到,代码如下:
可以看到 Repository 类的 unserialize 方法,调用的是 LogawareReflectionHelper 类的 unserialize 方法(如上图第5行代码),该方法我们可以在 engine\Shopware\Components\LogawareReflectionHelper.php 文件中找到,具体代码如下:
这里的 $serialized 就是我们刚刚传入的 sort (上图第3行),程序分别从 sort 中提取出值赋给 $className 和 $arguments 变量,然后这两个变量被传入 ReflectionHelper 类的 createInstanceFromNamedArguments 方法。该方法位于 engine\Shopware\Components\ReflectionHelper.php 文件,具体代码如下:
这里我们关注 第6行 代码,这里创建了一个反射类,而类的名称就是从 $sort 变量来的,可被用户控制利用。继续往下看,在代码第28行处用 $newParams 作为参数,创建一个新的实例对象。而这里的 $newParams 是从 $arguments[$paramName] 中取值的, $arguments 又是我们可以控制的,因为也是从 $sort 变量来,所以我们可以通过这里来实例化一个 SimpleXMLElement 类对象,形成一个XXE漏洞。下面,我们来看看具体如何利用这个漏洞。
漏洞利用
首先,我们需要登录后台,找到调用 loadPreviewAction 接口的位置,发现其调用位置如下:
当我们点击 Refresh preview 按钮时,就会调用 loadPreviewAction 方法,用BurpSuite抓到包如下:
1 | GET /shopware520/backend/ProductStream/loadPreview?_dc=1530963660916&sort={"Shopware\\Bundle\\SearchBundle\\Sorting\\PriceSorting":{"direction":"asc"}}&conditions={}&shopId=1¤cyId=1&customerGroupKey=EK&page=1&start=0&limit=2 |
我们可以看到 sort 值为 {"Shopware\\Bundle\\SearchBundle\\Sorting\\PriceSorting":{"direction":"asc"}} ,于是我们按照其格式构造payload: {"SimpleXMLElement":{"data":"http://localhost/xxe.xml","options":2,"data_is_url":1,"ns":"","is_prefix":0}} ,关于payload的含义,可以看看 SimpleXMLElement 类的 __construct 函数定义,具体点 这里 :
data
XML 字符串
options
可选参数,用于指定影响 XML 文档读取的额外 Libxml 参数。为2时即LIBXML_DTDLOAD可加载外部实体(DTD)
dataIsURL
默认值为 false。设置为 true 时,表示 data 参数是 XML 文档的路径或 URL,而不是字符串数据
namespaceOrPrefix
命名空间前缀或 URI
isPrefix
如果
namespaceOrPrefix是前缀,则设置为 true如果
namespaceOrPrefix是 URI,则设置为 false默认值为 false
笔者所用的xxe.xml内容如下:
1 |
|
我们发送payload,并用xdebug调试程序,最后程序将我们读取的值存储在 $conditions 变量中,如下图所示:
修复建议
关于PHP中XXE漏洞的修复,我们可以过滤关键词,如: ENTITY 、 SYSTEM 等,另外,我们还可以通过禁止加载XML实体对象的方式,来防止XXE漏洞(如下图第2行代码),具体代码如下:
libxml_disable_entity_loader(true); 这个函数在 PHP 中用于禁用 XML 解析时加载外部实体,是一种安全措施,用来防止 **XML 外部实体攻击 (XXE)**,这种攻击可能被恶意利用来读取敏感文件或执行其他恶意操作。
CTF例题练习
环境搭建
1 | // index.php |
1 | // f1agi3hEre.php |
WP
SimpleXMLElement加载外部实体
这道题目考察的是实例化漏洞结合XXE漏洞。审计代码可知$classname、$param、$param2三个参数都是可控的,而class_exists函数会判断类是否存在,如果不存在的话,就会调用程序中的 __autoload 函数,但是这里没有 __autoload 函数,而是用 spl_autoload_register 注册了一个类似 __autoload 作用的函数,即这里输出404信息。
这里我们能用的只有php内置类,所以我们先利用GlobIterator 类搜索 flag文件 名字:
1 | public GlobIterator::__construct ( string $pattern [, int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO ] ) |
$pattern (string):
这是一个包含文件匹配模式的字符串,通常类似于 glob() 函数的模式(如
*.txt、images/*.jpg)。它定义了要匹配的文件和目录的模式。你可以使用通配符,如 * 和 ?,来匹配文件名。
例如,$pattern 可能是 “images/*.jpg”,表示匹配所有 images 目录下的 .jpg 文件。$flags (int, 可选):
这是一个可选的标志参数,默认值为
FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO,这意味着文件路径将作为键,文件信息对象(SplFileInfo)作为值。
你可以通过不同的标志来调整返回的内容格式,例如:1
2
3
4FilesystemIterator::KEY_AS_PATHNAME: 迭代器将返回文件路径作为键。
FilesystemIterator::CURRENT_AS_FILEINFO: 迭代器将返回 SplFileInfo 对象作为当前的值,提供关于文件的信息。
FilesystemIterator::KEY_AS_FILENAME: 迭代器将返回文件名作为键。
FilesystemIterator::CURRENT_AS_PATHNAME: 迭代器将返回文件路径作为当前值。
第一个参数为要搜索的文件名,第二个参数为选择文件的哪个信息作为键名,这里我选择用 FilesystemIterator::CURRENT_AS_FILEINFO ,其对应的常量值为0,你可以在 这里 找到这些常量的值,所以最终搜索文件的 payload 如下:
1 | h?name=GlobIterator¶m=./*.php¶m2=0 |
这里我们知道了文件的名字为f1agi3hEre.php,接下来我们使用内置类 SimpleXMLElement 读取 f1agi3hEre.php 文件的内容,,这里我们要结合使用PHP流的使用,因为当文件中存在: < > & ‘ “ 这5个符号时,会导致XML文件解析错误,所以我们这里利用PHP文件流,将要读取的文件内容经过 base64编码 后输出即可,XML内容如下:
1 |
|
注意这个XML要url编码,payload:
1 | http://127.0.0.1/hongridaishen/day3/?name=SimpleXMLElement¶m=%3c%3fxml+version%3d%221.0%22%3f%3e%0a%3c!DOCTYPE+ANY+%5b+%0a+++++%3c!ENTITY+xxe+SYSTEM+%22php%3a%2f%2ffilter%2fread%3dconvert.base64-encode%2fresource%3d.%2ff1agi3hEre.php%22%3e%0a%5d%3e%0a%3cx%3e%26xxe%3b%3c%2fx%3e¶m2=2 |
上面payload中的param2=2,实际上这里2对应的模式是 LIBXML_NOENT ,具体可以参考 这里 。
LIBXML_NOENT:
LIBXML_NOENT 是 libxml2 库中的一个常量,表示在解析 XML 文档时要展开实体引用(如 &、< 等)为实际的字符。这意味着,如果 XML 文档中包含了实体引用,解析器会将它们转化为对应的字符值。具体来说,使用 LIBXML_NOENT 解析 XML 时,文档中的外部实体会被自动替换为它们的实际内容。
所以如果这里不设置为2的话时是得不到flag回显的
SplFileObject读取文件
SplFileObject原生类在这里同样可以用,SplFileObject 类和 SplFileinfo为单个文件的信息提供了一个高级的面向对象的接口,可以用于对文件内容的遍历、查找、操作等,所以SplFileObject同样可以读取文件,但一次只能读一行且需要知道文件名。如果要读取所有内容需要利用伪协议
而SplFileObject第二个参数默认为r,所以payload:
1 | http://127.0.0.1/hongridaishen/day3/?name=SplFileObject¶m=php://filter/convert.base64-encode/resource=./f1agi3hEre.php¶m2=r |
























