PHP用PDO防Sql注入注意事項

來源:互聯網
上載者:User

在PHP 5.3.6及以前版本中,並不支援在DSN中的charset定義,而應該使用PDO::MYSQL_ATTR_INIT_COMMAND設定初始SQL, 即我們常用的 set names gbk指令。


為何PDO能防SQL注入?
請先看以下PHP代碼:

 代碼如下 複製代碼

<?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 5.4.7

Mysql 協議版本 10

MySQL Server 5.5.27

 

以上代碼,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 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轉義呢?

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>指令。

以下是一段可防注入的範例程式碼:

 代碼如下 複製代碼

$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();
}

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.