翻译自 stackoverflow
使用预处理语句
和参数化查询
。这样的话,SQL语句会和参数分离开来,单独发送给数据库解析。使用这种方法的话,对任意攻击者来说,注入恶意的SQL都是不可能的。
你大体上有两种方式来实现上述目标:
1. 使用 PDO
1 | $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name'); |
2. 使用 MySQLi
1 | $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?'); |
注意当使用 PDO
访问MySQL数据库时,真正的预处理语句
默认是不启用的。要解决这个问题,你必须要关闭预处理语句
的模拟
选项。下面是使用PDO
创建数据库连接的例子:
1 | $dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass'); |
上面例子中的error mode
并不是必须的,但还是建议加上它。这样的话,当出错的时候,脚本不会由于发生Fatal Error
而停止。而且,这样做的话让开发者有机会捕获以PDOException
形式抛出的任何错误。
上面的例子中必须要有的是第一条 setAttribute()
语句,它告诉PDO关闭模拟的预处理语句,并且启用真正的预处理语句。这样做可以保证SQL语句和参数的值在发送给MySQL之前不会被PHP解析(让一切可能的攻击者没有机会注入恶意的SQL语句)。
尽管你也可以设置构造器中的charset
选项,但要特别注意的是,在老版本的的PHP(<5.3.6)中, DSN默认忽略charset
参数。
解释
你传递给prepare
的SQL语句被数据库解析并编译。通过指定参数(在上述的例子中使用?
或者命名参数:name
)来告诉数据库引擎你想过滤哪部分。而后,当你调用execute
时,预处理语句会和你所制定的参数的值组合在一起。
需要注意的是,参数的值是和编译后的语句组合在一起,不是和SQL字符串组合在一起。SQL注入工作的方式是,当脚本生成SQL并发送给数据库时,欺骗脚本,让其包含恶意的字符串。所以通过将发送的真正的SQL和参数隔离开来,你就可以降低发生SQL注入的风险。当使用预处理语句时,你所发送的任何参数都会被当作普通的字符串处理(当然,数据库引擎可能会做一些优化,参数最后有可能转化为了数字。)
使用预处理语句的另一个好处是,如果你在同一个会话中多次执行同一条语句,它只会被解析并编译一次,这样,你可以加快脚本的运行速度。
(结束)