防注入是程序員一個(gè)必須要了解的基本安全知道,下面我來介紹關(guān)于php使用pdo時(shí)的一些防注入安全知識(shí),希望此教程對(duì)你會(huì)有所幫助.
在PHP 5.3.6及以前版本中,并不支持在DSN中的charset定義,而應(yīng)該使用PDO::MYSQL_ATTR_INIT_COMMAND設(shè)置初始SQL,即我們常用的 set names gbk指令.
為何PDO能防SQL注入?請(qǐng)先看以下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);
- //開源代碼Vevb.com
- $st->execute();
- $st->fetchAll();
- ?>
環(huán)境如下:PHP 5.4.7,Mysql 協(xié)議版本 10,MySQL Server 5.5.27
以上代碼,PHP只是簡(jiǎn)單地將SQL直接發(fā)送給MySQL Server,其實(shí),這與我們平時(shí)使用mysql_real_escape_string將字符串進(jìn)行轉(zhuǎn)義,再拼接成SQL語句沒有差別,只是由PDO本地驅(qū)動(dòng)完成轉(zhuǎn)義的,顯然這種情況下還是有可能造成SQL注入的,也就是說在php本地調(diào)用pdo prepare中的mysql_real_escape_string來操作query,使用的是本地單字節(jié)字符集,而我們傳遞多字節(jié)編碼的變量時(shí),有可能還是會(huì)造成SQL注入漏洞(php 5.3.6以前版本的問題之一,這也就解釋了為何在使用PDO時(shí),建議升級(jí)到php 5.3.6+,并在DSN字符串中指定charset的原因.
針對(duì)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));
原因與上面的分析是一致的.
而正確的轉(zhuǎn)義應(yīng)該是給mysql Server指定字符集,并將變量發(fā)送給MySQL Server完成根據(jù)字符轉(zhuǎn)義.
那么,如何才能禁止PHP本地轉(zhuǎn)義而交由MySQL Server轉(zhuǎn)義呢?
PDO有一項(xiàng)參數(shù),名為PDO::ATTR_EMULATE_PREPARES,表示是否使用PHP本地模擬prepare,此項(xiàng)參數(shù)默認(rèn)值未知,php 5.3.6+默認(rèn)還是使用本地變量轉(zhuǎn),拼接成SQL發(fā)送給MySQL Server的,我們將這項(xiàng)值設(shè)置為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; //m.survivalescaperooms.com
- $name = 'zhangsan';
- $st->bindParam(1,$id);
- $st->bindParam(2,$name);
- $st->execute();
- $st->fetchAll();
- ?>
這次PHP是將SQL模板和變量是分兩次發(fā)送給MySQL的,由MySQL完成變量的轉(zhuǎn)義處理,既然變量和SQL模板是分兩次發(fā)送的,那么就不存在SQL注入的問題了,但需要在DSN中指定charset屬性,代碼如下:
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'root');
如此,即可從根本上杜絕SQL注入的問題.
使用PDO的注意事項(xiàng)
知道以上幾點(diǎn)之后,我們就可以總結(jié)使用PDO杜絕SQL注入的幾個(gè)注意事項(xiàng):
1.php升級(jí)到5.3.6+,生產(chǎn)環(huán)境強(qiáng)烈建議升級(jí)到php 5.3.9+ php 5.4+,php 5.3.8存在致命的hash碰撞漏洞.
2.若使用php 5.3.6+,請(qǐng)?jiān)谠赑DO的DSN中指定charset屬性.
3.如果使用了PHP 5.3.6及以前版本,設(shè)置PDO::ATTR_EMULATE_PREPARES參數(shù)為false(即由MySQL進(jìn)行變量處理),在DSN中指定charset是無效的,同時(shí)set names <charset>(此處詳細(xì)語句PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8')的執(zhí)行是必不可少的,php 5.3.6以上版本已經(jīng)處理了這個(gè)問題,無論是使用本地模擬prepare還是調(diào)用mysql server的prepare均可.
4.如果使用了PHP 5.3.6及以前版本,因Yii框架默認(rèn)并未設(shè)置ATTR_EMULATE_PREPARES的值,請(qǐng)?jiān)跀?shù)據(jù)庫配置文件中指定emulatePrepare的值為false。
那么,有個(gè)問題,如果在DSN中指定了charset,是否還需要執(zhí)行set names <charset>呢?
是的,不能省。set names <charset>其實(shí)有兩個(gè)作用:
A.告訴mysql server, 客戶端(PHP程序)提交給它的編碼是什么
B.告訴mysql server, 客戶端需要的結(jié)果的編碼是什么
也就是說,如果數(shù)據(jù)表使用gbk字符集,而PHP程序使用UTF-8編碼,我們?cè)趫?zhí)行查詢前運(yùn)行set names utf8, 告訴mysql server正確編碼即可,無須在程序中編碼轉(zhuǎn)換。這樣我們以u(píng)tf-8編碼提交查詢到mysql server, 得到的結(jié)果也會(huì)是utf-8編碼。省卻了程序中的轉(zhuǎn)換編碼問題,不要有疑問,這樣做不會(huì)產(chǎn)生亂碼。
那么在DSN中指定charset的作用是什么? 只是告訴PDO,本地驅(qū)動(dòng)轉(zhuǎn)義時(shí)使用指定的字符集(并不是設(shè)定mysql server通信字符集),設(shè)置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();
- }
新聞熱點(diǎn)
疑難解答