天天看點

從PDO下的SQL注入思路

基于PHP的SQL資料庫使用衆多,同時存在的SQL注入的風險也随之瘋漲,在SQL注入的攻防路上充滿了血雨驚風,防範方不斷改善防護能力,攻擊方不斷增強注入手段。各種注入手段的自動化進行數值型注入、字元型注入、報錯型注入、布爾型注入、時間型注入等探測;為了防止SQL注入,從PHP5.X版本以後PHP引進了一個PDO擴充接口用來查詢、擷取SQL數量,進而杜絕SQL注入;從原理上分析PDO擴充的攻與防。

一、什麼是PDO?

PDO是PHP5.x以後新加入的一個重大功能,因為在PHP5.x以前的php4/php3都是一堆的資料庫擴充來跟各個資料庫的連接配接和處理,例如php_mysql.dll、php_pgsql.dll、php_mssql.dll、php_sqlite.dll等等擴充來連接配接MySQL、PostgreSQL、MSSQLServer、SQLite,同樣的,我們必須借助ADOdb、PEAR:B、PHPlib:B之類的資料庫抽象類來幫助我們,無比煩瑣和低效,畢竟,php代碼的效率怎麼能夠比我們直接用C/C++寫的擴充斜率高呐?是以,PDO的出現是必然的。

二、使用PDO擴充的方法

需要在php.ini檔案進行配置,配置如下,之後重新開機重新開機我們的Web伺服器生效。

從PDO下的SQL注入思路

三、PDO擴充防止SQL注入的方法

1、 調用queote()方法轉義特殊字元,類似addslashes、php_sql_esacap等,将輸入的字元串中的特殊字元進行轉義。

2、 占位符(:、?)-通過指令參數防止注入:sql參數注入、會将輸入的參數值當作字元串整體進行處理,其中的特殊字元會被當作字元處理,不會當作分隔符;例如 ’ 、/、#、

Select * from where name=:username and password=:password

Select * from user where name=? and password=?

3、 通過bindParam()方法綁定參數防禦SQL注入

$sql='select * from user where name=:username and password=:password';(該條為sql語句,:username等為命名參數占字元)

$stmt=$pdo->prepare($sql);(預編譯,将$sql送入mysql資料庫)

$stmt->bindParam(":username",$username,PDO::PARAM_STR);

$stmt->bindParam(":password",$password,PDO::PARAM_STR);

(bindparam()将綁定參數類型設定為PDO::PARAM_STR字元型)

DPO擴充對SQL注入的語句都進行了轉義、預編譯、限制參數類型等等操作,那作為攻擊者還有應對的良方嘛?

PDO擴充在配置上存在出現漏洞的可能,漏洞總是存在于程式員粗心。

四、 目前在PDO下,SQL注入方法

比較通用的手法主要有如下三種:

1、 寬位元組注入

PHP中編碼為GBK,函數執行添加的是ASCII編碼(添加的符号為“\”),MYSQL預設字元集是GBK等寬位元組字元集。大家都知道%df’ 被PHP轉義(開啟GPC、用addslashes函數,或者icov等),單引号被加上反斜杠\,變成了 %df\’,其中\的十六進制是 %5C ,那麼現在 %df\’ =%df%5c%27,如果程式的預設字元集是GBK等寬位元組字元集,則MySQL用GBK的編碼時,會認為 %df%5c 是一個寬字元,也就是表,也就是說:%df\’ = %df%5c%27=縗’,有了單引号就好注入了。寬位元組注入原理即是利用編碼轉換,将伺服器端強制添加的本來用于轉義的\符号吃掉,進而能使攻擊者輸入的引号起到閉合作用,以至于可以進行SQL注入。

從PDO下的SQL注入思路
從PDO下的SQL注入思路

PHP和SQL資料庫采用的是GBK編碼(預設utf-8),html采用的是utf-8,MySQL采用的是寬位元組集,大于128時候回将轉義的%5C與%df進行結合編碼->漢字。

2、 堆疊注入

2.1 模拟預處理模式

PDO分為模拟預處理和非模拟預處理兩種分式。模拟預處理:防止有些資料庫類型不支援預處理而設定。設定方式:在初始化PDO驅動時,可以設定一項參數,PDO::ATTR_EMULATE_PREPARES,作用是打開模拟預處理(true)或者關閉(false),預設為true。PDO内部會模拟參數綁定的過程,SQL語句是在最後execute()的時候才發送給資料庫執行。

PDO安全問題的預設設定如下

PDO::ATTR_EMULATE_PREPARES //模拟預處理(預設開啟)

PDO::ATTR_ERRMODE //報錯

PDO::MYSQL_ATTR_MULTI_STATEMENTS //允許多句執行(預設開啟)

其實,這與我們平時使用mysql_real_escape_string()将字元串進行轉義,再拼接成SQL語句沒有差别,隻是由PDO本地驅動完成轉義的(EMULATE_PREPARES),如下圖所示:

從PDO下的SQL注入思路

PDO預設是允許多句執行和模拟預編譯的,在使用者輸入參數可控的情況下,會導堆疊注入。

如果想禁止多語句執行,可在建立PDO執行個體時将PDO::MYSQL_ATTR_MULTI_STATEMENTS設定為false;但是哪怕禁止了多語句執行,也隻是防範了堆疊注入而已,直接union即可。

2.2 非模拟預處理模式

非模拟預處理是是通過資料庫伺服器來進行預處理動作主要分為兩步:

第一步是prepare階段,發送SQL語句模闆到資料庫伺服器;

第二步通過execute()函數發送占位符參數給資料庫伺服器執行。

此時堆疊注入已經無法使用,但是在inline query 存在報錯注入。

根據以下mysql封包進行驗證如下:

從PDO下的SQL注入思路
從PDO下的SQL注入思路

3、 報錯注入

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

3.1無論是否開啟PDO::ATTR_EMULATE_PREPARES-模拟預處理,此時SQL語句如果産生報錯,PDO則會将報錯抛出;除設定錯誤碼之外,PDO 還将抛出一個 PDOException 異常類并設定它的屬性來反射錯誤碼和錯誤資訊。

3.2在調試期間也非常有用,因為它會有效地放大腳本中産生錯誤的點,進而可以非常快速地指出代碼中有問題的潛在區域,在這種情況下可以實作error-based SQL Injection。使用GTID_SUBSET函數進行報錯注入。

五、 總結

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本地進行拼接會存在SQL注入風險;對于SQL語句和參數進行兩次發送SQL server端進行拼接執行,且參數進行類型設定,不存在SQL注入風險。