PHP审计之WeEngine审计
前言
审计该CMS加深一下对于MVC架构的php审计流程
梳理路由
打开代码看到index.php文件
if($_W[\'os\'] == \'mobile\' && (!empty($_GPC[\'i\']) || !empty($_SERVER[\'QUERY_STRING\']))) {header(\'Location: ./app/index.php?\' . $_SERVER[\'QUERY_STRING\']);} else {header(\'Location: ./web/index.php?\' . $_SERVER[\'QUERY_STRING\']);}
web端会跳转到
/web/index.php
,来看到
web/index.php
if (($_W[\'setting\'][\'copyright\'][\'status\'] == 1) && empty($_W[\'isfounder\']) && $controller != \'cloud\' && $controller != \'utility\' && $controller != \'account\') {$_W[\'siteclose\'] = true;if ($controller == \'account\' && $action == \'welcome\') {template(\'account/welcome\');exit;}if ($controller == \'user\' && $action == \'login\') {if (checksubmit()) {require _forward($controller, $action);}template(\'user/login\');exit;}isetcookie(\'__session\', \'\', -10000);message(\'站点已关闭,关闭原因:\' . $_W[\'setting\'][\'copyright\'][\'reason\'], url(\'account/welcome\'), \'info\');}
这里
$controller
和
$action
接收是从
bootstrap.inc.php
$controller = $_GPC[\'c\'];$action = $_GPC[\'a\'];$do = $_GPC[\'do\'];
上面接收
$controller
和
$action
为
account
和
welcome
调用
template(\'account/welcome\');
function template($filename, $flag = TEMPLATE_DISPLAY) {global $_W;$source = IA_ROOT . "/web/themes/{$_W[\'template\']}/{$filename}.html";$compile = IA_ROOT . "/data/tpl/web/{$_W[\'template\']}/{$filename}.tpl.php";if(!is_file($source)) {$source = IA_ROOT . "/web/themes/default/{$filename}.html";$compile = IA_ROOT . "/data/tpl/web/default/{$filename}.tpl.php";}...
这段代码实际上就是加载一个模板渲染。
继续往下看
$controllers = array();$handle = opendir(IA_ROOT . \'/web/source/\');if(!empty($handle)) {while($dir = readdir($handle)) {if($dir != \'.\' && $dir != \'..\') {$controllers[] = $dir;}}}if(!in_array($controller, $controllers)) {$controller = \'account\';}$init = IA_ROOT . "/web/source/{$controller}/__init.php";if(is_file($init)) {require $init;}$actions = array();$handle = opendir(IA_ROOT . \'/web/source/\' . $controller);if(!empty($handle)) {while($dir = readdir($handle)) {if($dir != \'.\' && $dir != \'..\' && strexists($dir, \'.ctrl.php\')) {$dir = str_replace(\'.ctrl.php\', \'\', $dir);$actions[] = $dir;}}}if(empty($actions)) {header(\'location: ?refresh\');}if(!in_array($action, $actions)) {$action = $acl[$controller][\'default\'];}if(!in_array($action, $actions)) {$action = $actions[0];}
遍历读取
/web/source/
,所有内容。
遍历读取
/web/source/\' . $controller
,并且把内容中的
.ctrl.php
去掉。
if(is_array($acl[$controller][\'direct\']) && in_array($action, $acl[$controller][\'direct\'])) {require _forward($controller, $action);exit;
判断是否为数组并且判断
$acl[$controller][\'direct\']
,并且查看
$action
是否在
$acl
截取对应
$controller
的
direct
。
逻辑其实就是
$acl
中定义了大量的数组,如
\'account\' => array(\'default\' => \'welcome\',\'direct\' => array(\'welcome\',\'auth\')
$controller=account
direct=array(\’welcome\’,\’auth\’)
direct这个数组对应的是account下面的路由
访问welcome的路由的访问策略即
/web/index.php?c=account&a=welcome
漏洞审计
定位到漏洞位置
web/source/site/category.ctrl.php
定位到176行
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)).")");}
file_delete($row[\'icon\']);
,这里的
$row[\'icon\']
是通过遍历
$navs
$navs
是通过一下这个sql语句查询得来的
$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\');
找看数据库中的这两个字段是否可控
site_nav数据库表中对应的数据是
$nav
变量内容,发现
$nav[\'icon\']
变量是从
$_GPC[\'iconfile\']
来的,即参数可控。这里的
$nav[\'icon\']
变量,其实就是我们文章开头分析的传入
file_delete
函数的参数
if(!empty($nav_exist)) {pdo_update(\'site_nav\', $nav, array(\'id\' => $category[\'nid\'], \'uniacid\' => $_W[\'uniacid\']));} else {pdo_insert(\'site_nav\', $nav);
这里需要先把文件名插入到数据库中,然后调用该功能将文件上传,代码会从数据库从查找该文件名,然后删除对应文件。
参考
https://github.com/hongriSec/PHP-Audit-Labs/tree/master/Part1/Day6/files