首页 > 参数化查询为什么可以避免sql注入呢?

参数化查询为什么可以避免sql注入呢?

1.问题描述
参数化查询为什么可以避免sql注入呢?

2.补充说明
关于sql注入事实上有2个问题,第一,什么是sql注入?第二,如何避免sql注入?
第一个问题网上的资料说的很清楚,这个容易理解。
但是如何避免sql注入呢,归结为一句话,就是使用参数化查询,而不要使用字符串拼接————可是不管是参数化查询(即PreparedStatement的占位符),还是拼接字符串,最终不都是要执行一个一模一样的sql语句吗?

例如:

"SELECT * FROM users WHERE (name = '1' OR '1'='1') and (pw = '1' OR '1'='1');"

不知道有没有说清楚我的问题。。。


语言:php
预处理可用驱动类型:mysqlipdo,推荐pdo
使用占位能避免的原因是,你的值的部分如果有特殊符号会进行转义,如' ",会转义为\' \",1 or 1=1应该会过滤掉or及后面的(or这点只是猜测没实践)。转义过滤参数,防止语句被截断逻辑绕过执行其他语句,如or,union,ascii编码绕过过滤等
你用wireshark抓包,如果用的pdo驱动:

当然也可以做字符串拼接,只要进行严格过滤参数,比如是int,字符串长度,自己写个枚举数组过滤,addslashes()过滤特殊符号等,如果能非常严谨的做过滤可以字符串拼接,要么就直接驱动换pdo,进行预处理高枕无忧

PDO::ATTR_EMULATE_PREPARES:

PDO::ATTR_EMULATE_PREPARES Enables or disables emulation of prepared statements. Some drivers do not support native prepared statements or have limited support for them. Use this setting to force PDO to either always emulate prepared statements (if TRUE), or to try to use native prepared statements (if FALSE). It will always fall back to emulating the prepared statement if the driver cannot successfully prepare the current query


看到目前答案太水了,作为顶尖高手,我来回答一下。

首先,SQL语言,是一种Shell语言,这种Shell语言只是为了管理员、运维方便测试与管理而设计的,程序员在写程序时,不能也不应该使用。但大家都图方便,于是程序员在程序里也用了SQL,这是注入问题的导火索。在程序中,不仅不能用SQL,所有Shell语言都不能使用,因为都存在注入问题。

其次,程序访问数据库,实质上是一种通信问题。涉及到通信,就牵涉到通信协议。比如:

SELECT ID FROM TestTable WHERE Name = 'abc'这种数据库查询,实际上必须由数据库驱动封装打包为:

struct 通信协议
{
    模型:SELECT A FROM B WHERE C = D
    参数A:"ID"
    参数B:"TestTable"
    参数C:"Name"
    参数D:"abc"
}

数据库驱动把struct打包后,再发送给数据库,就不存在注入问题,这是数据库驱动的设计原理,同时也是不会被注入的原因。

那些说什么转义,你们也是够了,转义的话,你能把一款数据库的所有SQL特殊语法结构都给转义了吗?而且数据库一旦升级之后可能又会引入新的语法结构,程序不改的话可能会引入新的注入位置。

那些说什么抓包的,你们也是够了。抓包看到的东西并不能代表什么,因为数据库驱动能以任何方式发送命令。比如,数据库驱动觉得当前命令太简单,没必要封包,就直接发原始SQL语句过去;遇到复杂的、存在注入问题的命令,才封包发送。


拼接字符串和参数化查询到底有什么区别?
1)拼接字符串
SELECT * FROM users WHERE (name = '1' OR '1'='1') and (pw = '1' OR '1'='1')
说明:拼接字符串时,单引号这个字符是被数据库厂商当作单引号使用的。
2)参数化查询

delete from user where user.id='w' or '2'='2';

说明:参数化查询时,数据库厂商的jdbc.jar里的PreparedStatement.setString(1,"值"),会对参数的每一个字符进行转义,具体来说,除了最外层的2个单引号其他的单引号'都被转义成',数据库厂商如果碰到单引号'是把该单引号这个字符当做单引号使用的,而碰到'是把它当作值使用的。

参考:http://blog.csdn.net/yan46594...


$stmt = $db->prepare("SELECT `username` FROM `pb_users` WHERE `id`=?");
$stmt->bind_param('i', $id);
$stmt->execute();


使用预处理参数化查询的网络交互是这样的:
在 Request Prepare Statement 里可以看到

SELECT `username` FROM `pb_users` WHERE `id`=?

在 Request Execute Statement 里可以看到 Parameter 内容为:

Type: FIELD_TYPE_LONGLONG (8)
Unsigned: 0
Value: 1

可见PHP将SQL模板和变量分两次发送给MySQL,由MySQL完成变量的转义处理.
既然SQL模板和变量是分两次发送的,那么就不存在SQL注入的问题了.
在MySQL的general_log里可以看到:

Prepare SELECT `username` FROM `pb_users` WHERE `id`=?
Execute SELECT `username` FROM `pb_users` WHERE `id`=1

http://php.net/manual/zh/pdo....
很多更成熟的数据库都支持预处理语句的概念.
什么是预处理语句?可以把它看作是想要运行的SQL的一种编译过的模板,它可以使用变量参数进行定制.
预处理语句可以带来两大好处:
1.查询仅需解析(或预处理)一次,但可以用相同或不同的参数执行多次.
当查询准备好后,数据库将分析/编译/优化执行该查询的计划.
对于复杂的查询,此过程要花费较长的时间,如果需要以不同参数多次重复相同的查询,那么该过程将大大降低应用程序的速度.
通过使用预处理语句,可以避免重复分析/编译/优化周期.
简言之,预处理语句占用更少的资源,因而运行得更快.
2.提供给预处理语句的参数不需要用引号括起来,驱动程序会自动处理.
如果应用程序只使用预处理语句,可以确保不会发生SQL注入.
然而,如果查询的其他部分是由未转义的输入来构建的,则仍存在SQL注入的风险.

预处理语句如此有用,以至于它们唯一的特性是在驱动程序不支持的时候,PDO将模拟处理.
这样可以确保不管数据库是否具有这样的功能,都可以确保应用程序可以用相同的数据访问模式.

使用PDO操作MySQL前,关闭模拟预处理,才能使用真正的预处理:
$dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
保证先发送SQL模板给MySQL编译,然后发送参数给MySQL套进SQL模板里执行.
MySQLi默认情况下的预处理就是真正的预处理,不需要额外指定.
这些都可以通过MySQL的general_log日志或者WireShark观察到.

【热门文章】
【热门文章】