防注入是程序員一個必需要了解的基本安全知道了,下面我來介紹關於php使用pdo時的一些防注入安全知識。php
在這裏,我簡單的表示爲: 收到指令 -> 編譯SQL生成執行計劃 ->選擇執行計劃 ->執行執行計劃。html
具體可能有點不同,但大體的步驟如上所示。mysql
在PHP 5.3.6及之前版本中,並不支持在DSN中的charset定義,而應該使用PDO::MYSQL_ATTR_INIT_COMMAND設置初始SQL, 即咱們經常使用的 set names gbk指令程序員
<?php $pdo = new PDO("mysql:host=192.168.0.1;dbname=test;charset=utf8","root"); $st = $pdo->prepare("select * from info where id =? and name = ?"); $id = 21; $name = 'zhangsan'; $st->bindParam(1,$id); $st->bindParam(2,$name); $st->execute(); $st->fetchAll(); ?>
以上代碼,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的緣由。sql
針對php 5.3.6之前版本,如下代碼仍然可能形成SQL注入問題:數據庫
$pdo->query('SET NAMES GBK'); $var = chr(0xbf) . chr(0x27) . " OR 1=1 /*"; $query = "SELECT * FROM info WHERE name = ?"; $stmt = $pdo->prepare($query); $stmt->execute(array($var));
緣由與上面的分析是一致的。安全
而正確的轉義應該是給mysql Server指定字符集,並將變量發送給MySQL Server完成根據字符轉義。框架
那麼,如何才能禁止PHP本地轉義而交由MySQL Server轉義呢?fetch
PDO有一項參數,名爲PDO::ATTR_EMULATE_PREPARES ,表示是否使用PHP本地模擬prepare,此項參數默認值未知。php 5.3.6+默認仍是使用本地變量轉,拼接成SQL發送給MySQL Server的,咱們將這項值設置爲false, 試試效果,如如下代碼:編碼
<?php $pdo = new PDO("mysql:host=192.168.0.1;dbname=test;","root"); $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); $st = $pdo->prepare("select * from info where id =? and name = ?"); $id = 21; //www.111Cn.nEt $name = 'zhangsan'; $st->bindParam(1,$id); $st->bindParam(2,$name); $st->execute(); $st->fetchAll(); ?>
此次PHP是將SQL模板和變量是分兩次發送給MySQL的,由MySQL完成變量的轉義處理,既然變量和SQL模板是分兩次發送的,那麼就不存在SQL注入的問題了,但須要在DSN中指定charset屬性,如:
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root');
如此,便可從根本上杜絕SQL注入的問題。
使用PDO的注意事項
知道以上幾點以後,咱們就能夠總結使用PDO杜絕SQL注入的幾個注意事項:
1. php升級到5.3.6+,生產環境強烈建議升級到php 5.3.9+ php 5.4+,php 5.3.8存在致命的hash碰撞漏洞。
2. 若使用php 5.3.6+, 請在在PDO的DSN中指定charset屬性
3. 若是使用了PHP 5.3.6及之前版本,設置PDO::ATTR_EMULATE_PREPARES參數爲false(即由MySQL進行變量處理),在DSN中指定 charset是無效的,同時set names <charset>(此處詳細語句PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8')的執行是必不可少的。(php 5.3.6以上版本已經處理了這個問題,不管是使用本地模擬prepare仍是調用mysql server的prepare都可。)
4. 若是使用了PHP 5.3.6及之前版本, 因Yii框架默認並未設置ATTR_EMULATE_PREPARES的值,請在數據庫配置文件中指定emulatePrepare的值爲false。
那麼,有個問題,若是在DSN中指定了charset, 是否還須要執行set names <charset>呢?
是的,不能省。set names <charset>其實有兩個做用:
A. 告訴mysql server, 客戶端(PHP程序)提交給它的編碼是什麼
B. 告訴mysql server, 客戶端須要的結果的編碼是什麼
也就是說,若是數據表使用gbk字符集,而PHP程序使用UTF-8編碼,咱們在執行查詢前運行set names utf8, 告訴mysql server正確編碼便可,無須在程序中編碼轉換。這樣咱們以utf-8編碼提交查詢到mysql server, 獲得的結果也會是utf-8編碼。省卻了程序中的轉換編碼問題,不要有疑問,這樣作不會產生亂碼。
那麼在DSN中指定charset的做用是什麼? 只是告訴PDO, 本地驅動轉義時使用指定的字符集(並非設定mysql server通訊字符集),設置mysql server通訊字符集,還得使用set names <charset>指令。
如下是一個完整的code:
$dbhost="localhost"; $dbname="test"; $dbusr="root"; $dbpwd=""; $dbhdl=NULL; $dbstm=NULL; $opt = array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',); $dsn='mysql:host=' . $dbhost . ';dbname=' . $dbname.';charset=utf8'; try { $dbhdl = new PDO($dsn, $dbusr, $dbpwd, $opt);//www.111cn.net $dbhdl=->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); //dbhdl->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_SILENT);//Display none //dbhdl->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_WARNING);//Display warning $dbhdl->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);//Display exception } catch (PDOExceptsddttrtion $e) {//return PDOException print "Error!: " . $e->getMessage() . "<br>"; die(); } $dbhost="localhost"; $dbname="test"; $dbusr="root"; $dbpwd=""; $dbhdl=NULL; $dbstm=NULL; $dsn='mysql:host=' . $dbhost . ';dbname=' . $dbname.';charset=utf8'; try { $dbhdl = new PDO($dsn, $dbusr, $dbpwd,); $dbhdl=->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); //dbhdl->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_SILENT);//Display none //dbhdl->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_WARNING);//Display warning $dbhdl->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);//Display exception $dbhdl->query('SET NAMES GBK'); } catch (PDOExceptsddttrtion $e) {//return PDOException print "Error!: " . $e->getMessage() . "<br>"; die(); }