PHP的SQL注入技术实现以及预防措施
為什么80%的碼農(nóng)都做不了架構(gòu)師?>>> ??
SQL 攻擊(SQL injection,臺(tái)灣稱作SQL資料隱碼攻擊),簡稱注入攻擊,是發(fā)生于應(yīng)用程序之?dāng)?shù)據(jù)庫層的安全漏洞。簡而言之,是在輸入的字符串之中注入SQL指 令,在設(shè)計(jì)不良的程序當(dāng)中忽略了檢查,那么這些注入進(jìn)去的指令就會(huì)被數(shù)據(jù)庫服務(wù)器誤認(rèn)為是正常的SQL指令而運(yùn)行,因此遭到破壞。
有部份人認(rèn)為SQL注入攻擊是只針對(duì)Microsoft SQL Server而來,但只要是支持批處理SQL指令的數(shù)據(jù)庫服務(wù)器,都有可能受到此種手法的攻擊。
1、原因
? ? 在應(yīng)用程序中若有下列狀況,則可能應(yīng)用程序正暴露在SQL Injection的高風(fēng)險(xiǎn)情況下:
? ? 在應(yīng)用程序中使用字符串聯(lián)結(jié)方式組合SQL指令。
? ? 在應(yīng)用程序鏈接數(shù)據(jù)庫時(shí)使用權(quán)限過大的賬戶(例如很多開發(fā)人員都喜歡用sa(內(nèi)置的最高權(quán)限的系統(tǒng)管理員賬戶)連接Microsoft SQL Server數(shù)據(jù)庫)。
? ? 在數(shù)據(jù)庫中開放了不必要但權(quán)力過大的功能(例如在Microsoft SQL Server數(shù)據(jù)庫中的xp_cmdshell延伸預(yù)存程序或是OLE Automation預(yù)存程序等)
? ? 太過于信任用戶所輸入的數(shù)據(jù),未限制輸入的字符數(shù),以及未對(duì)用戶輸入的數(shù)據(jù)做潛在指令的檢查。
2、作用原理
? ? ?SQL命令可查詢、插入、更新、刪除等,命令的串接。而以分號(hào)字符為不同命令的區(qū)別。(原本的作用是用于SubQuery或作為查詢、插入、更新、刪除……等的條件式)
? ? SQL命令對(duì)于傳入的字符串參數(shù)是用單引號(hào)字符所包起來?!兜B續(xù)2個(gè)單引號(hào)字符,在SQL數(shù)據(jù)庫中,則視為字符串中的一個(gè)單引號(hào)字符》
? ? SQL命令中,可以注入注解《連續(xù)2個(gè)減號(hào)字符 -- 后的文字為注解,或“/*”與“*/”所包起來的文字為注解》
? ? 因此,如果在組合SQL的命令字符串時(shí),未針對(duì)單引號(hào)字符作取代處理的話,將導(dǎo)致該字符變量在填入命令字符串時(shí),被惡意竄改原本的SQL語法的作用。
SQL 注入攻擊的主要原因,是因?yàn)橐韵聝牲c(diǎn)原因:
? ? 1. php 配置文件 php.ini 中的 magic_quotes_gpc選項(xiàng)沒有打開,被置為 off;
? ? 2. 開發(fā)者沒有對(duì)數(shù)據(jù)類型進(jìn)行檢查和轉(zhuǎn)義。
不過事實(shí)上,第二點(diǎn)最為重要。
我認(rèn)為, 對(duì)用戶輸入的數(shù)據(jù)類型進(jìn)行檢查,向 MYSQL 提交正確的數(shù)據(jù)類型,這應(yīng)該是一個(gè) web 程序員最最基本的素質(zhì)。但現(xiàn)實(shí)中,常常有許多小白式的 Web 開發(fā)者忘了這點(diǎn),從而導(dǎo)致后門大開。
為什么說第二點(diǎn)最為重要?因?yàn)槿绻麤]有第二點(diǎn)的保證,magic_quotes_gpc 選項(xiàng),不論為 on,還是為 off,都有可能引發(fā) SQL 注入攻擊。
下面來看一下技術(shù)實(shí)現(xiàn):
一、 magic_quotes_gpc= Off 時(shí)的注入攻擊
? ?? ?magic_quotes_gpc = Off 是 php 中一種非常不安全的選項(xiàng)。新版本的 php 已經(jīng)將默認(rèn)的值改為了 On。但仍有相當(dāng)多的服務(wù)器的選項(xiàng)為 off。畢竟,再古董的服務(wù)器也是有人用的。
? ?? ?當(dāng)magic_quotes_gpc = On 時(shí),它會(huì)將提交的變量中所有的 '(單引號(hào))、"(雙號(hào)號(hào))、(反斜線)、空白字符,都會(huì)在前面自動(dòng)加上 。下面是 PHP的官方說明:
magic_quotes_gpc boolean
Sets the magic_quotes state for GPC (Get/Post/Cookie) operations.?
When magic_quotes are on, all ' (single-quote), " (double quote), (backslash)??and NUL's are escaped with a backslash automatically。
如果沒有轉(zhuǎn)義,即 off 情況下,就會(huì)讓攻擊者有機(jī)可乘。以下列測試腳本為例:
<? if (isset($_POST["f_login"])) { // 連接數(shù)據(jù)庫... // ...代碼略... // 檢查用戶是否存在 $t_strUname = $_POST["f_uname"]; $t_strPwd = $_POST["f_pwd"]; $t_strSQL = "SELECT * FROM tbl_users WHERE username='$t_strUname' AND password = '$t_strPwd' LIMIT 0,1"; if ($t_hRes = mysql_query($t_strSQL)) { // 成功查詢之后的處理. 略... } } ?> <html><head><title>test</title></head> <body> <form method="post" action=""> Username: <input type="text" name="f_uname" size=30><br> Password: <input type=text name="f_pwd" size=30><br> <input type="submit" name="f_login" value="登錄"> </form> </body>在這個(gè)腳本中,當(dāng)用戶輸入正常的用戶名和密碼,假設(shè)值分別為 zhang3、abc123,則提交的 SQL 語句如下:
SELECT * FROM tbl_users WHERE username='zhang3' AND password = 'abc123' LIMIT 0,1如果攻擊者在 username 字段中輸入:zhang3' OR 1=1 #,在 password 輸入 abc123,則提交的 SQL 語句變成如下:
SELECT * FROM tbl_users WHERE username='zhang3' OR 1=1 #' AND password = 'abc123' LIMIT 0,1由于 # 是 mysql中的注釋符, #之后的語句不被執(zhí)行,實(shí)現(xiàn)上這行語句就成了:
SELECT * FROM tbl_users WHERE username='zhang3' OR 1=1 這樣攻擊者就可以繞過認(rèn)證了。如果攻擊者知道數(shù)據(jù)庫結(jié)構(gòu),那么它構(gòu)建一個(gè) UNION SELECT,那就更危險(xiǎn)了:
假設(shè)在 username 中輸入:
在password 輸入: abc123,
則提交的 SQL 語句變成:
這樣就相當(dāng)危險(xiǎn)了。
二、magic_quotes_gpc = On 時(shí)的注入攻擊
? ? 當(dāng) magic_quotes_gpc = On 時(shí),攻擊者無法對(duì)字符型的字段進(jìn)行 SQL 注入。這并不代表這就安全了。這時(shí),可以通過數(shù)值型的字段進(jìn)行SQL注入。
在最新版的 MYSQL 5.x 中,已經(jīng)嚴(yán)格了數(shù)據(jù)類型的輸入,已默認(rèn)關(guān)閉自動(dòng)類型轉(zhuǎn)換。數(shù)值型的字段,不能是引號(hào)標(biāo)記的字符型。
也就是說,假設(shè) uid 是數(shù)值型的,在以前的 mysql 版本中,這樣的語句是合法的:
INSERT INTO tbl_user SET uid="1"; SELECT * FROM tbl_user WHERE uid="1";在最新的 MYSQL 5.x 中,上面的語句不是合法的,必須寫成這樣:
INSERT INTO tbl_user SET uid=1; SELECT * FROM tbl_user WHERE uid=1;這樣我認(rèn)為是正確的。因?yàn)樽鳛殚_發(fā)者,向數(shù)據(jù)庫提交正確的符合規(guī)則的數(shù)據(jù)類型,這是最基本的要求
那么攻擊者在 magic_quotes_gpc = On 時(shí),他們?cè)趺垂裟?#xff1f;很簡單,就是對(duì)數(shù)值型的字段進(jìn)行 SQL 注入。以下列的 php 腳本為例:
<? if (isset($_POST["f_login"])) { // 連接數(shù)據(jù)庫... // ...代碼略... // 檢查用戶是否存在 $t_strUid = $_POST["f_uid"]; $t_strPwd = $_POST["f_pwd"]; $t _strSQL = "SELECT * FROM tbl_users WHERE uid=$t_strUid AND password = '$t_strPwd' LIMIT 0,1"; if ($t_hRes = mysql_query($t_strSQL)) { // 成功查詢之后的處理. 略... } } ?> <html><head><title>test</title></head> <body> <form method="post" action=""> User ID: <input type="text" name="f_uid" size=30><br> Password: <input type=text name="f_pwd" size=30><br> <input type="submit" name="f_login" value="登錄"> </form> </body> </html>上面這段腳本要求用戶輸入 userid 和 password 登入。一個(gè)正常的語句,用戶輸入 1001和abc123,提交的 sql 語句如下:
SELECT * FROM tbl_users WHERE userid=1001 AND password = 'abc123' LIMIT 0,1如果攻擊者在 userid 處,輸入:1001 OR 1 =1 #,則注入的sql語句如下:
SELECT * FROM tbl_users WHERE userid=1001 OR 1 =1 # AND password = 'abc123' LIMIT 0,1攻擊者達(dá)到了目的。
三、如何防止 PHP的SQL 注入攻擊
如何防止 php sql 注入攻擊?我認(rèn)為最重要的一點(diǎn),就是要對(duì)數(shù)據(jù)類型進(jìn)行檢查和轉(zhuǎn)義??偨Y(jié)的幾點(diǎn)規(guī)則如下:
? ? 1. php.ini 中的 display_errors 選項(xiàng),應(yīng)該設(shè)為 display_errors = off。這樣 php 腳本出錯(cuò)之后,不會(huì)在 web 頁面輸出錯(cuò)誤,以免讓攻擊者分析出有作的信息。
? ? 2. 調(diào)用 mysql_query 等 mysql 函數(shù)時(shí),前面應(yīng)該加上 @,即 @mysql_query(...),這樣 mysql 錯(cuò)誤不會(huì)被輸出。同理以免讓攻擊者分析出有用的信息。
另外,有些程序員在做開發(fā)時(shí),當(dāng) mysql_query出錯(cuò)時(shí),習(xí)慣輸出錯(cuò)誤以及 sql 語句,例如:
<php $t_strSQL = "SELECT a from b...."; if (mysql_query($t_strSQL)) { // 正確的處理 } else { echo "錯(cuò)誤! SQL 語句:$t_strSQL 錯(cuò)誤信息" . mysql_query(); exit; } ?>這種做法是相當(dāng)危險(xiǎn)和愚蠢的。如果一定要這么做,最好在網(wǎng)站的配置文件中,設(shè)一個(gè)全局變量或定義一個(gè)宏,設(shè)一下 debug 標(biāo)志:
<?php //全局配置文件中: define("DEBUG_MODE", 0); // 1: DEBUG MODE; 0: RELEASE MODE //調(diào)用腳本中: $t_strSQL = "SELECT a from b...."; if (mysql_query($t_strSQL)) { // 正確的處理 } else { if (DEBUG_MODE) { echo "錯(cuò)誤! SQL 語句:$t_strSQL錯(cuò)誤信息" . mysql_query(); } exit; } ?>? ? 3. 對(duì)提交的 sql 語句,進(jìn)行轉(zhuǎn)義和類型檢查。
四、寫一個(gè)安全參數(shù)獲取函數(shù)
為了防止用戶的錯(cuò)誤數(shù)據(jù)和 php + mysql 注入 ,我寫了一個(gè)函數(shù) PAPI_GetSafeParam(),用來獲取安全的參數(shù)值:
<?php define("XH_PARAM_INT", 0); define("XH_PARAM_TXT", 1); function PAPI_GetSafeParam($pi_strName, $pi_Def = "", $pi_iType = XH_PARAM_TXT) { if (isset($_GET[$pi_strName])) { $t_Val = trim($_GET[$pi_strName]); } else if (isset($_POST[$pi_strName])) { $t_Val = trim($_POST[$pi_strName]); } else { return $pi_Def; } // INT if (XH_PARAM_INT == $pi_iType) { if (is_numeric($t_Val)) { return $t_Val; } else { return $pi_Def; } } // String $t_Val = str_replace("&", "&", $t_Val); $t_Val = str_replace("<", "<", $t_Val); $t_Val = str_replace(">", ">", $t_Val); if (get_magic_quotes_gpc()) { $t_Val = str_replace(""", """, $t_Val); $t_Val = str_replace("''", "'", $t_Val); } else { $t_Val = str_replace(""", """, $t_Val); $t_Val = str_replace("'", "'", $t_Val); } return $t_Val; } ?>在這個(gè)函數(shù)中,有三個(gè)參數(shù):
$pi_strName:變量名
$pi_Def:默認(rèn)值
$pi_iType: 數(shù)據(jù)類型。取值為 XH_PARAM_INT,XH_PARAM_TXT,分別表示數(shù)值型和文本型。
如果請(qǐng)求是數(shù)值型,那么調(diào)用 is_numeric() 判斷是否為數(shù)值。如果不是,則返回程序指定的默認(rèn)值。
簡單起見,對(duì)于文本串,我將用戶輸入的所有危險(xiǎn)字符(包括HTML代碼),全部轉(zhuǎn)義。
由于 php 函數(shù) addslashes()存在漏洞,我用 str_replace()直接替換。get_magic_quotes_gpc( ) 函數(shù)是 php 的函數(shù),用來判斷 magic_quotes_gpc 選項(xiàng)是否打開。
剛才第二節(jié)的示例,代碼可以這樣調(diào)用:
<?phpif (isset($_POST["f_login"])) {// 連接數(shù)據(jù)庫...// ...代碼略...// 檢查用戶是否存在$t_strUid = PAPI_GetSafeParam("f_uid", 0, XH_PARAM_INT);$t_strPwd = PAPI_GetSafeParam("f_pwd", "", XH_PARAM_TXT);$t_strSQL = "SELECT * FROM tbl_users WHERE uid=$t_strUid AND password = '$t_strPwd' LIMIT 0,1";if ($t_hRes = mysql_query($t_strSQL)) {// 成功查詢之后的處理. 略...}}?>這樣的話,就已經(jīng)相當(dāng)安全了。
轉(zhuǎn)載于:https://my.oschina.net/mik3y/blog/291597
總結(jié)
以上是生活随笔為你收集整理的PHP的SQL注入技术实现以及预防措施的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 孕妇梦到插秧是什么意思
- 下一篇: 梦到很多黄鳝是什么意思