最近在折騰 PHP + MYSQL 的程式設計。了解了一些 PHP SQL 注入攻擊的知識,于是寫了這篇文章 http://www.xiaohui.com/weekly/20070314.htm,總結一下經驗。在我看來,引發 SQL 注入攻擊的主要原因,是因為以下兩點原因:
1. php 配置檔案 php.ini 中的 magic_quotes_gpc 選項沒有打開,被置為 off
2. 開發者沒有對資料類型進行檢查和轉義
不過事實上,第二點最為重要。我認為, 對使用者輸入的資料類型進行檢查,向 MYSQL 送出正确的資料類型,這應該是一個 web 程式員最最基本的素質。但現實中,常常有許多小白式的 Web 開發者忘了這點, 進而導緻後門大開。
為什麼說第二點最為重要?因為如果沒有第二點的保證,magic_quotes_gpc 選項,不論為 on,還是為 off,都有可能引發 SQL 注入攻擊。下面來看一下技術實作:
magic_quotes_gpc = Off 是 php 中一種非常不安全的選項。新版本的 php 已經将預設的值改為了 On。但仍有相當多的伺服器的選項為 off。畢竟,再古董的伺服器也是有人用的。
當magic_quotes_gpc = On 時,它會将送出的變量中所有的 '(單引号)、"(雙号号)、\(反斜線)、空白字元,都為在前面自動加上 \。下面是 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
如果沒有轉義,即 off 情況下,就會讓攻擊者有機可乘。以下列測試腳本為例:
http://www.xiaohui.com/weekly/20070314.htm
在這個腳本中,當使用者輸入正常的使用者名和密碼,假設值分别為 zhang3、abc123,則送出的 SQL 語句如下:
如果攻擊者在 username 字段中輸入:zhang3' OR 1=1 #,在 password 輸入 abc123,則送出的 SQL 語句變成如下:
由于 # 是 mysql中的注釋符, #之後的語句不被執行,實作上這行語句就成了:
這樣攻擊者就可以繞過認證了。如果攻擊者知道資料庫結構,那麼它建構一個 UNION SELECT,那就更危險了:
假設在 username 中輸入:zhang3 ' OR 1 =1 UNION select cola, colb,cold FROM tbl_b #
在password 輸入: abc123,
則送出的 SQL 語句變成:
這樣就相當危險了。如果agic_quotes_gpc選項為 on,引号被轉義,則上面攻擊者建構的攻擊語句就會變成這樣,進而無法達到其目的:
當 magic_quotes_gpc = On 時,攻擊者無法對字元型的字段進行 SQL 注入。這并不代表這就安全了。這時,可以通過數值型的字段進行SQL注入。
在最新版的 MYSQL 5.x 中,已經嚴格了資料類型的輸入,已預設關閉自動類型轉換。數值型的字段,不能是引号标記的字元型。也就是說,假設 uid 是數值型的,在以前的 mysql 版本中,這樣的語句是合法的:
在最新的 MYSQL 5.x 中,上面的語句不是合法的,必須寫成這樣:
這樣我認為是正确的。因為作為開發者,向資料庫送出正确的符合規則的資料類型,這是最基本的要求。
那麼攻擊者在 magic_quotes_gpc = On 時,他們怎麼攻擊呢?很簡單,就是對數值型的字段進行 SQL 注入。以下列的 php 腳本為例:
上面這段腳本要求使用者輸入 userid 和 password 登入。一個正常的語句,使用者輸入 1001和abc123,送出的 sql 語句如下:
如果攻擊者在 userid 處,輸入:1001 OR 1 =1 #,則注入的sql語句如下:
攻擊者達到了目的。
如何防止 php sql 注入攻擊?我認為最重要的一點,就是要對資料類型進行檢查和轉義。總結的幾點規則如下:
php.ini 中的 display_errors 選項,應該設為 display_errors = off。這樣 php 腳本出錯之後,不會在 web 頁面輸出錯誤,以免讓攻擊者分析出有作的資訊。
調用 mysql_query 等 mysql 函數時,前面應該加上 @,即 @mysql_query(...),這樣 mysql 錯誤不會被輸出。同理以免讓攻擊者分析出有用的資訊。另外,有些程式員在做開發時,當 mysql_query出錯時,習慣輸出錯誤以及 sql 語句,例如:
這種做法是相當危險和愚蠢的。如果一定要這麼做,最好在網站的配置檔案中,設一個全局變量或定義一個宏,設一下 debug 标志:
對送出的 sql 語句,進行轉義和類型檢查。
為了防止使用者的錯誤資料和 php + mysql 注入 ,我寫了一個函數 PAPI_GetSafeParam(),用來擷取安全的參數值:
在這個函數中,有三個參數:
如果請求是數值型,那麼調用 is_numeric() 判斷是否為數值。如果不是,則傳回程式指定的預設值。
簡單起見,對于文本串,我将使用者輸入的所有危險字元(包括HTML代碼),全部轉義。由于 php 函數 addslashes()存在漏洞,我用 str_replace()直接替換。get_magic_quotes_gpc() 函數是 php 的函數,用來判斷 magic_quotes_gpc 選項是否打開。
剛才第二節的示例,代碼可以這樣調用:
這樣的話,就已經相當安全了。PAPI_GetSafeParam的代碼有點長,但犧牲這點效率,對保證安全,是值得的。希望大家多批評指正。:)
本文轉自yunlielai51CTO部落格,原文連結:http://blog.51cto.com/4925054/1071891,如需轉載請自行聯系原作者