开始练习【红日团队】的PHP-Audit-Labs 代码审计 Day6
链接:https://github.com/hongriSec/PHP-Audit-Labs
感兴趣的同学可以去练习练习
预备知识:
内容题目均来自 PHP SECURITY CALENDAR 2017
Day 6 - Frost Pattern代码如下:
class TokenStorage {
public function performAction($action, $data) {
switch ($action) {
case 'create':
$this->createToken($data);
break;
case 'delete':
$this->clearToken($data);
break;
default:
throw new Exception('Unknown action');
}
}
public function createToken($seed) {
$token = md5($seed);
file_put_contents('/tmp/tokens/' . $token, '...data');
}
public function clearToken($token) {
$file = preg_replace("/[^a-z.-_]/", "", $token);
unlink('/tmp/tokens/' . $file);
}
}
$storage = new TokenStorage();
$storage->performAction($_GET['action'], $_GET['data']);
漏洞解析 :
这一关考察的内容是由正则表达式
不严谨导致的任意文件删除漏洞
, 导致这一漏洞的原因在 第21行
:
$file = preg_replace("/[^a-z.-_]/", "", $token);
该正则表达式并未起到过滤目录路径字符的作用。[^a-z.-_]
表示匹配除了a
字符到z
字符、.
字符到 _
字符之间的所有字符。因此,攻击者还是可以使用点
和斜杠
符号进行路径穿越,最终删除任意文件
,例如使用 payload : action = delete&data = ../../ config.php
,便可删除 config.php
文件。
preg_replace() 函数:
(PHP 4, PHP 5, PHP 7)
功能:
preg_replace 函数执行一个正则表达式的搜索和替换。
定义:
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]])
搜索 subject
中匹配pattern
的部分, 以replacement
进行替换。
说明:
参数 | 描述 |
---|---|
pattern | 要搜索的模式,可以是字符串或一个字符串数组 |
replacement | 用于替换的字符串或字符串数组 |
subject | 要搜索替换的目标字符串或字符串数组。 |
limit | 可选,对于每个模式用于每个 subject 字符串的最大可替换次数。 默认是-1(无限制) |
count | 可选,为替换执行的次数。 |
返回值:
如果subject
是一个数组, preg_replace()
返回一个数组, 其他情况下返回一个字符串。
如果匹配被查找到,替换后的subject
被返回,其他情况下 返回没有改变的subject
。如果发生错误,返回 NULL
。
范例:
实例分析:
本次实例分析,我们选取的是
WeEngine0.8
版本。
漏洞POC 本站提供安全工具、程序(方法)可能带有攻击性,仅供安全研究与教学之用,风险自负!
漏洞分析:
漏洞入口文件为 web/source/site/category.ctrl.php
(169-184行) ,
} elseif ($do == 'delete') {
load()->func('file');
$id = intval($_GPC['id']);
$category = pdo_fetch("SELECT id, parentid, nid FROM ".tablename('site_category')." WHERE id = '$id'");
if (empty($category)) {
message('抱歉,分类不存在或是已经被删除!', url('site/category/display'), 'error');
}
$navs = pdo_fetchall("SELECT icon, id FROM ".tablename('site_nav')." WHERE id IN (SELECT nid FROM ".tablename('site_category')." WHERE id = {$id} OR parentid = '$id')", array(), 'id');
if (!empty($navs)) {
foreach ($navs as $row) {
file_delete($row['icon']);
}
pdo_query("DELETE FROM ".tablename('site_nav')." WHERE id IN (".implode(',', array_keys($navs)).")");
}
pdo_delete('site_category', array('id' => $id, 'parentid' => $id), 'OR');
message('分类删除成功!', url('site/category'), 'success');
我们可以看到下图 11行 处调用了file_delete
函数,而这是一个文件删除
相关操作,我们可以看一下该函数的具体定义。
if (!empty($navs)) {
foreach ($navs as $row) {
file_delete($row['icon']);
}
file_delete
这一函数可以在framework/function/file.func.php
(352-363行)文件中找到,该方法功能用于检测文件是否存在,如果存在,则删除文件。
function file_delete($file) {
if (empty($file)) {
return FALSE;
}
if (file_exists($file)) {
@unlink($file);
}
if (file_exists(ATTACHMENT_ROOT . '/' . $file)) {
@unlink(ATTACHMENT_ROOT . '/' . $file);
}
return TRUE;
}
但是查看上下文发现,程序并没有对文件名 $file
变量进行过滤,所以文件名就可以存在类似../
这种字符,这样也就引发任意文件删除漏洞。
现在我们在回溯回去,看看 $file
变量从何处来。实际上,上面的$file
变量对应的是 $row['icon']
的值:
file_delete($row['icon']);
也就是说如果我们可以控制$row['icon']
的值,就可以删除任意文件。那么我们来看看 $row
变量从何而来。该变量就在我们刚刚分析的( web/source/site/category.ctrl.php 文件中(177-182行)):
if (!empty($navs)) {
foreach ($navs as $row) {
file_delete($row['icon']);
}
pdo_query("DELETE FROM ".tablename('site_nav')." WHERE id IN (".implode(',', array_keys($navs)).")");
}
该值为变量 $navs
中的元素值:
分析代码,即可找到$navs
变量的取值情况。可以看到 $navs
变量的是从数据库 site_nav
表中取出的,包含了icon
和id
两个字段,具体代码(176行)如下:
$navs = pdo_fetchall("SELECT icon, id FROM ".tablename('site_nav')." WHERE id IN (SELECT nid FROM ".tablename('site_category')." WHERE id = {$id} OR parentid = '$id')", array(), 'id');
现在我们要做的,就是找找看数据库中的这两个字段是否可以被用户控制。我们继续往前查找,发现了如下代码(140-146行):
if(!empty($nav_exist)) {
pdo_update('site_nav', $nav, array('id' => $category['nid'], 'uniacid' => $_W['uniacid']));
} else {
pdo_insert('site_nav', $nav);
$nid = pdo_insertid();
$data['nid'] = $nid;
}
site_nav
表中的数据,对应的是 $nav
变量。我们继续往上寻找 $nav
变量,发现 $nav['icon']
变量是从 $_GPC['iconfile']
来的,即可被用户控制( 下图 第21行 )。
这里的 $nav['icon']
变量,其实就是我们文章开头分析的传入 file_delete
函数的参数.
由于 $nav['icon']
变量可被用户控制,程序有没有对其进行消毒处理,直接就传入了 file_delete
函数,最终导致了文件删除漏洞。至此,我们分析完了整个漏洞的发生过程,接下看看如何进行攻击。
漏洞利用:
源文件里面的环境发现安装不了后面找了一个新的环境
百度云盘分享地址:http://pan.baidu.com/s/1jIONcN0
提取密码vp3k
环境安装完后,进入页面:
http://10.211.55.5/PHPcode/WeEngine/1/web/index.php?c=account&a=display
再进入管理公众号:
找到分类设置,点击添加文章分类。
这里对应的url为:
http://10.211.55.5/PHPcode/WeEngine/1/web/index.php?c=site&a=category&
,实际上表示 site
控制器的 category
模块,即对应category.ctrl.php
文件。
添加分类:
选择对应的内容,进入 if($isnav) 判断:
在上传图标位置输入要删除文件的路径
我们建立 delete.txt 文件,用于测试任意文件删除:
我们点击删除时,就会调用file_delete
函数,同时就会删除掉我们插入到数据库中的图片名:
这个类型任意文件删除有点类似于二次注入
,在添加分类时先把要删除的文件名称插入到数据库中,然后点击删除分类时,会从数据库中取出要删除的文件名。
修复建议:
漏洞是没有对 $row['icon']
参数进行过滤,可以将文件名内容加入目录阶层字符,造成任意文件删除漏洞,所以我们要在传入的参数中过滤"../"
等目录阶层字符,避免目录穿越,删除其他文件夹下文件。我们在修复中可以过滤掉$row['icon']
中的目录穿越字符,引入我们自定义的一个函数checkstr
函数。同时$row['icon']
只是文件的名称,并非是一个路径,因此过滤字符并不会影响到实际功能,对此修复意见我们提供如下代码:
结语
再次感谢【红日团队】