ThinkPHPSQL注入

前段时间先知上的一篇文章

https://xz.aliyun.com/t/6758

成因:传入参数未进行过滤

thinkphp的MVC架构中,Controller函数的变量也作为GET/POST传参的方式,如 http://serverName/index.php/Home/Blog/archive/year/2013/month/11 我们即可访问到 public function archive($year=’2013’,$month=’01’)。而这个URL同样可以写为http://serverName/index.php?c=Blog&a=archive&year=2013&month=11,那么同样可以写为http://serverName/index.php?c=Blog&a=archive&year=2013&month[0]=exp&month[1]=sqli。这样传递的参数是不会经过I函数的,所以I函数里的过滤也没有效果。

https://bugs.leavesongs.com/php/thinkphp%E6%A1%86%E6%9E%B6%E6%9E%B6%E6%9E%84%E4%B8%8A%E5%AD%98%E5%9C%A8sql%E6%B3%A8%E5%85%A5/

跟踪下TP的处理流程吧~

TP3.2:

自己写一个文件

(模型类命名为除去表前缀的数据表名称,采用驼峰法命名,并且首字母大写,然后加上模型层的名称(默认定义是Model) 例: UserModel.class.php)

1
2
3
4
5
public function login(){
$User = D('Users');
$map = array('user_name' => $_POST['username']);
$user = $User->where($map)->find();
}

首先跟踪Where

1
2
3
4
5
6
7
……
if(isset($this->options['where'])){
$this->options['where'] = array_merge($this->options['where'],$where);
}else{
$this->options['where'] = $where;
}
……

简单的将where放入options[where]里

再来看看find

跟踪到

1
$resultSet          =   $this->db->select($options);

继续跟踪select

1
2
3
4
5
6
7
public function select($options=array()) {
$this->model = $options['model'];
$this->parseBind(!empty($options['bind'])?$options['bind']:array());
$sql = $this->buildSelectSql($options);
$result = $this->query($sql,!empty($options['fetch_sql']) ? true : false);
return $result;
}

跟进buildSelectSql

1
$sql  =   $this->parseSql($this->selectSql,$options);

跟进parseSql

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public function parseSql($sql,$options=array()){
$sql = str_replace(
array('%TABLE%','%DISTINCT%','%FIELD%','%JOIN%','%WHERE%','%GROUP%','%HAVING%','%ORDER%','%LIMIT%','%UNION%','%LOCK%','%COMMENT%','%FORCE%'),
array(
$this->parseTable($options['table']),
$this->parseDistinct(isset($options['distinct'])?$options['distinct']:false),
$this->parseField(!empty($options['field'])?$options['field']:'*'),
$this->parseJoin(!empty($options['join'])?$options['join']:''),
$this->parseWhere(!empty($options['where'])?$options['where']:''),
$this->parseGroup(!empty($options['group'])?$options['group']:''),
$this->parseHaving(!empty($options['having'])?$options['having']:''),
$this->parseOrder(!empty($options['order'])?$options['order']:''),
$this->parseLimit(!empty($options['limit'])?$options['limit']:''),
$this->parseUnion(!empty($options['union'])?$options['union']:''),
$this->parseLock(isset($options['lock'])?$options['lock']:false),
$this->parseComment(!empty($options['comment'])?$options['comment']:''),
$this->parseForce(!empty($options['force'])?$options['force']:'')
),$sql);
return $sql;
}

因为我们是where,所以跟进parseWhere

1
$whereStr .= $this->parseWhereItem($this->parseKey($key),$val);

继续跟进parseWhereItem

1
2
3
4
5
elseif('bind' == $exp ){ // 使用表达式
$whereStr .= $key.' = :'.$val[1];
}elseif('exp' == $exp ){ // 使用表达式
$whereStr .= $key.' '.$val[1];
}

传入的数组第一位是exp或者bind的时候都可直接将数组第二位带入SQL语句,先来看下exp的

img

这里数组第二位未传参,可传任意参数

bind的时候会带上: 需要满足其他条件才能造成注入

img

1
2
3
4
5
6
public function login(){
$User = D('Users');
$user['id'] = I('id');
$data['user_name'] = I('user_name');
$user = $User->where($user)->save($data);&
}

传入参数

1
id[]=bind&id[]=0'&username=1234

即可