天天看点

从PDO下的SQL注入思路

基于PHP的SQL数据库使用众多,同时存在的SQL注入的风险也随之疯涨,在SQL注入的攻防路上充满了血雨惊风,防范方不断改善防护能力,攻击方不断增强注入手段。各种注入手段的自动化进行数值型注入、字符型注入、报错型注入、布尔型注入、时间型注入等探测;为了防止SQL注入,从PHP5.X版本以后PHP引进了一个PDO扩展接口用来查询、获取SQL数量,从而杜绝SQL注入;从原理上分析PDO扩展的攻与防。

一、什么是PDO?

PDO是PHP5.x以后新加入的一个重大功能,因为在PHP5.x以前的php4/php3都是一堆的数据库扩展来跟各个数据库的连接和处理,例如php_mysql.dll、php_pgsql.dll、php_mssql.dll、php_sqlite.dll等等扩展来连接MySQL、PostgreSQL、MSSQLServer、SQLite,同样的,我们必须借助ADOdb、PEAR:B、PHPlib:B之类的数据库抽象类来帮助我们,无比烦琐和低效,毕竟,php代码的效率怎么能够比我们直接用C/C++写的扩展斜率高呐?所以,PDO的出现是必然的。

二、使用PDO扩展的方法

需要在php.ini文件进行配置,配置如下,之后重启重启我们的Web服务器生效。

从PDO下的SQL注入思路

三、PDO扩展防止SQL注入的方法

1、 调用queote()方法转义特殊字符,类似addslashes、php_sql_esacap等,将输入的字符串中的特殊字符进行转义。

2、 占位符(:、?)-通过命令参数防止注入:sql参数注入、会将输入的参数值当作字符串整体进行处理,其中的特殊字符会被当作字符处理,不会当作分隔符;例如 ’ 、/、#、

Select * from where name=:username and password=:password

Select * from user where name=? and password=?

3、 通过bindParam()方法绑定参数防御SQL注入

$sql='select * from user where name=:username and password=:password';(该条为sql语句,:username等为命名参数占字符)

$stmt=$pdo->prepare($sql);(预编译,将$sql送入mysql数据库)

$stmt->bindParam(":username",$username,PDO::PARAM_STR);

$stmt->bindParam(":password",$password,PDO::PARAM_STR);

(bindparam()将绑定参数类型设置为PDO::PARAM_STR字符型)

DPO扩展对SQL注入的语句都进行了转义、预编译、限制参数类型等等操作,那作为攻击者还有应对的良方嘛?

PDO扩展在配置上存在出现漏洞的可能,漏洞总是存在于程序员粗心。

四、 目前在PDO下,SQL注入方法

比较通用的手法主要有如下三种:

1、 宽字节注入

PHP中编码为GBK,函数执行添加的是ASCII编码(添加的符号为“\”),MYSQL默认字符集是GBK等宽字节字符集。大家都知道%df’ 被PHP转义(开启GPC、用addslashes函数,或者icov等),单引号被加上反斜杠\,变成了 %df\’,其中\的十六进制是 %5C ,那么现在 %df\’ =%df%5c%27,如果程序的默认字符集是GBK等宽字节字符集,则MySQL用GBK的编码时,会认为 %df%5c 是一个宽字符,也就是表,也就是说:%df\’ = %df%5c%27=縗’,有了单引号就好注入了。宽字节注入原理即是利用编码转换,将服务器端强制添加的本来用于转义的\符号吃掉,从而能使攻击者输入的引号起到闭合作用,以至于可以进行SQL注入。

从PDO下的SQL注入思路
从PDO下的SQL注入思路

PHP和SQL数据库采用的是GBK编码(默认utf-8),html采用的是utf-8,MySQL采用的是宽字节集,大于128时候回将转义的%5C与%df进行结合编码->汉字。

2、 堆叠注入

2.1 模拟预处理模式

PDO分为模拟预处理和非模拟预处理两种分式。模拟预处理:防止有些数据库类型不支持预处理而设置。设置方式:在初始化PDO驱动时,可以设置一项参数,PDO::ATTR_EMULATE_PREPARES,作用是打开模拟预处理(true)或者关闭(false),默认为true。PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行。

PDO安全问题的默认设置如下

PDO::ATTR_EMULATE_PREPARES //模拟预处理(默认开启)

PDO::ATTR_ERRMODE //报错

PDO::MYSQL_ATTR_MULTI_STATEMENTS //允许多句执行(默认开启)

其实,这与我们平时使用mysql_real_escape_string()将字符串进行转义,再拼接成SQL语句没有差别,只是由PDO本地驱动完成转义的(EMULATE_PREPARES),如下图所示:

从PDO下的SQL注入思路

PDO默认是允许多句执行和模拟预编译的,在用户输入参数可控的情况下,会导堆叠注入。

如果想禁止多语句执行,可在创建PDO实例时将PDO::MYSQL_ATTR_MULTI_STATEMENTS设置为false;但是哪怕禁止了多语句执行,也只是防范了堆叠注入而已,直接union即可。

2.2 非模拟预处理模式

非模拟预处理是是通过数据库服务器来进行预处理动作主要分为两步:

第一步是prepare阶段,发送SQL语句模板到数据库服务器;

第二步通过execute()函数发送占位符参数给数据库服务器执行。

此时堆叠注入已经无法使用,但是在inline query 存在报错注入。

根据以下mysql报文进行验证如下:

从PDO下的SQL注入思路
从PDO下的SQL注入思路

3、 报错注入

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

3.1无论是否开启PDO::ATTR_EMULATE_PREPARES-模拟预处理,此时SQL语句如果产生报错,PDO则会将报错抛出;除设置错误码之外,PDO 还将抛出一个 PDOException 异常类并设置它的属性来反射错误码和错误信息。

3.2在调试期间也非常有用,因为它会有效地放大脚本中产生错误的点,从而可以非常快速地指出代码中有问题的潜在区域,在这种情况下可以实现error-based SQL Injection。使用GTID_SUBSET函数进行报错注入。

五、 总结

PHP与数据库交互只是简单地将SQL语句直接发送给MySQL Server ,其实,这与我们平时使用mysql_real_escape_string()将字符串进行转义,再拼接成SQL语句没有差别(只是由PDO本地驱动完成转义的),显然这种情况下还是有可能造成SQL注入的,也就是说在php本地调用pdo prepare中的mysql_real_escape_string()来操作query,使用的是本地单字节字符集,而我们传递多字节编码的变量时,有可能还是会造成SQL注入漏洞(php 5.3.6以前版本的问题之一,这也就解释了为何在使用PDO时,建议升级到php 5.3.6+,并在DSN字符串中指定charset的原因。

PHP本地进行拼接会存在SQL注入风险;对于SQL语句和参数进行两次发送SQL server端进行拼接执行,且参数进行类型设置,不存在SQL注入风险。