PHP審計之WeEngine審計

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$actionaccountwelcome

調用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截取對應$controllerdirect

邏輯其實就是$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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章