PHP代碼實現防sql注入

防注入是程序員一個必需要了解的基本安全知道了,下面我來介紹關於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();
}
相關文章
相關標籤/搜索