一個簡單的SQL注入攻擊案例
假如我們有一個公司網站,在網站的後台資料庫中儲存了所有的客戶資料等重要訊息。假如網站登入頁面的代碼中有這樣一條命令來讀取使用者資訊。
代碼如下 |
複製代碼 |
<? $q = "SELECT `id` FROM `users` WHERE `username`= ' " .$_GET['username']. " ' AND `password`= ' " .$_GET['password']. " ' "; ?> |
現在有一個駭客想攻擊你的資料庫,他會嘗試在此登入頁面的使用者名稱的輸入框中輸入以下代碼:
代碼如下 |
複製代碼 |
' ; SHOW TABLES; |
點擊登陸鍵,這個頁面就會顯示出資料庫中的所有表。如果他現在使用下面這行命令:
代碼如下 |
複製代碼 |
'; DROP TABLE [table name]; |
這樣他就把一張表刪除了!
當然,這隻是一個很簡單的例子,實際的SQL注入方法比這個要複雜得多,駭客也願意花大量的時間來不斷嘗試來攻擊你的代碼。有一些程式軟體也可以自動地來不斷嘗試SQL注入攻擊。瞭解了SQL注入的攻擊原理後,我們來看一下如何防範SQL注入攻擊。
magic_quotes_gpc = On 時的注入攻擊
當 magic_quotes_gpc = On 時,攻擊者無法對字元型的欄位進行 SQL 注入。這並不代表這就安全了。這時,可以通過數值型的欄位進行SQL注入。
在最新版的 MYSQL 5.x 中,已經嚴格了資料類型的輸入,已預設關閉自動類型轉換。數值型的欄位,不能是引號標記的字元型。也就是說,假設 uid 是數值型的,在以前的 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; |
這樣我認為是正確的。因為作為開發人員,向資料庫提交正確的符合規則的資料類型,這是最基本的要求。
那麼攻擊者在 magic_quotes_gpc = On 時,他們怎麼攻擊呢?很簡單,就是對數值型的欄位進行 SQL 注入。以下列的 php 指令碼為例:
代碼如下 |
複製代碼 |
<? if ( isset($_POST["f_login"] ) ) { // 串連資料庫... // ...代碼略... // 檢查使用者是否存在 $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>sample 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> |
上面這段指令碼要求使用者輸入 userid 和 password 登入。一個正常的語句,使用者輸入 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
|
攻擊者達到了目的。
防範SQL注入 - 使用mysql_real_escape_string()函數
在資料庫操作的代碼中用這個函數mysql_real_escape_string()可以將代碼中特殊字元過濾掉,如引號等。如下例:
代碼如下 |
複製代碼 |
<? $q = "SELECT `id` FROM `users` WHERE `username`= ' " .mysql_real_escape_string( $_GET['username'] ). " ' AND `password`= ' " .mysql_real_escape_string( $_GET['password'] ). " ' "; ?> |
防範SQL注入 - 使用mysql_query()函數
mysql_query()的特別是它將只執行SQL代碼的第一條,而後面的並不會執行。回想在最前面的例子中,駭客通過代碼來例後台執行了多條SQL命令,顯示出了所有表的名稱。所以mysql_query()函數可以取到進一步保護的作用。我們進一步演化剛才的代碼就得到了下面的代碼:
代碼如下 |
複製代碼 |
<? //connection $database = mysql_connect("localhost", "username","password"); //db selection mysql_select_db("database", $database); $q = mysql_query("SELECT `id` FROM `users` WHERE `username`= ' " .mysql_real_escape_string( $_GET['username'] ). " ' AND `password`= ' " .mysql_real_escape_string( $_GET['password'] ). " ' ", $database); ?> |
除此之外,我們還可以在PHP代碼中判斷輸入值的長度,或者專門用一個函數來檢查輸入的值。所以在接受使用者輸入值的地方一定要做好輸入內容的過濾和檢查。當然學習和瞭解最新的SQL注入方式也非常重要,這樣才能做到有目的的防範。如果使用的是平台式的網站系統如Wordpress,要注意及時打上官方的補丁或升級到新的版本。如果有講得不對的地方或不理解的請在評論區留言。
php.ini 中的 display_errors 選項,應該設為 display_errors = off。這樣 php 指令碼出錯之後,不會在 web 頁面輸出錯誤,以免讓攻擊者分析出有作的資訊。
調用 mysql_query 等 mysql 函數時,前面應該加上 @,即 @mysql_query(...),這樣 mysql 錯誤不會被輸出。同理以免讓攻擊者分析出有用的資訊。另外,有些程式員在做開發時,當 mysql_query出錯時,習慣輸出錯誤以及 sql 語句,例如:
代碼如下 |
複製代碼 |
$t_strSQL = "SELECT a from b...."; if ( mysql_query($t_strSQL) ) { // 正確的處理 } else { echo "錯誤! SQL 陳述式:$t_strSQL rn錯誤資訊".mysql_query(); exit; } |
這種做法是相當危險和愚蠢的。如果一定要這麼做,最好在網站的設定檔中,設一個全域變數或定義一個宏,設一下 debug 標誌:
全域設定檔中:
代碼如下 |
複製代碼 |
define("DEBUG_MODE",0); // 1: DEBUG MODE; 0: RELEASE MODE |
//呼叫指令碼中:
代碼如下 |
複製代碼 |
$t_strSQL = "SELECT a from b...."; if ( mysql_query($t_strSQL) ) { // 正確的處理 } else { if (DEBUG_MODE) echo "錯誤! SQL 陳述式:$t_strSQL rn錯誤資訊".mysql_query(); exit; } |
關於 sql防注入內容 http://www.111cn.net/phper/phpanqn/37704.htm