【漏洞练习-Day6】WeEngine0.8 正则使用不当导致的路径穿越问题

开始练习【红日团队】的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行) ,

扫描二维码关注公众号,回复: 8943770 查看本文章
} 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表中取出的,包含了iconid 两个字段,具体代码(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'] 只是文件的名称,并非是一个路径,因此过滤字符并不会影响到实际功能,对此修复意见我们提供如下代码:
在这里插入图片描述

结语

再次感谢【红日团队】

发布了35 篇原创文章 · 获赞 19 · 访问量 5198

猜你喜欢

转载自blog.csdn.net/zhangpen130/article/details/103948078
今日推荐