sql注入攻擊

沉默術士發表於2017-07-03
SQL攻擊(SQL injection,臺灣稱作SQL資料隱碼攻擊),簡稱注入攻擊,是發生於應用程式之資料庫層的安全漏洞。簡而言之,是在輸入的字串之中注入SQL指令,在設計不良的程式當中忽略了檢查,那麼這些注入進去的指令就會被資料庫伺服器誤認為是正常的SQL指令而執行,因此遭到破壞。
  有部份人認為SQL隱碼攻擊攻擊是隻針對Microsoft SQL Server而來,但只要是支援批處理SQL指令的資料庫伺服器,都有可能受到此種手法的攻擊。
  原因
  在應用程式中若有下列狀況,則可能應用程式正暴露在SQL Injection的高風險情況下:
  在應用程式中使用字串聯結方式組合SQL指令。
  在應用程式連結資料庫時使用許可權過大的賬戶(例如很多開發人員都喜歡用sa(內建的最高許可權的系統管理員賬戶)連線Microsoft SQL Server資料庫)。
  在資料庫中開放了不必要但權力過大的功能(例如在Microsoft SQL Server資料庫中的xp_cmdshell延伸預存程式或是OLE Automation預存程式等)
  太過於信任使用者所輸入的資料,未限制輸入的字元數,以及未對使用者輸入的資料做潛在指令的檢查。
  作用原理
  SQL命令可查詢、插入、更新、刪除等,命令的串接。而以分號字元為不同命令的區別。(原本的作用是用於SubQuery或作為查詢、插入、更新、刪除……等的條件式)
  SQL命令對於傳入的字串引數是用單引號字元所包起來。《但連續2個單引號字元,在SQL資料庫中,則視為字串中的一個單引號字元》
  SQL命令中,可以注入註解《連續2個減號字元 — 後的文字為註解,或“/*”與“*/”所包起來的文字為註解》
  因此,如果在組合SQL的命令字串時,未針對單引號字元作取代處理的話,將導致該字元變數在填入命令字串時,被惡意竄改原本的SQL語法的作用。
  以上內容摘自維基百科@
  最近翻到一本有關SQL隱碼攻擊與防禦的一本書,當然這本書內容很長,我還只讀了前兩章部分,後續我會慢慢把書裡的知識梳理到我的部落格中來!
  SQL 注入攻擊的主要原因,是因為以下兩點原因:
  1. php 配置檔案 php.ini 中的 magic_quotes_gpc選項沒有開啟,被置為 off;
  2. 開發者沒有對資料型別進行檢查和轉義。
  不過事實上,第二點最為重要。我認為, 對使用者輸入的資料型別進行檢查,向 MYSQL 提交正確的資料型別,這應該是一個 web 程式設計師最最基本的素質。但現實中,常常有許多小白式的 Web 開發者忘了這點,從而導致後門大開。
  為什麼說第二點最為重要?因為如果沒有第二點的保證,magic_quotes_gpc 選項,不論為 on,還是為 off,都有可能引發 SQL 注入攻擊。下面來看一下技術實現:
  一、 magic_quotes_gpc= Off 時的注入攻擊
  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 情況下,就會讓攻擊者有機可乘。以下列測試指令碼為例:
<?
if (isset($_POST[“f_login”])) {
// 連線資料庫…
// …程式碼略…
// 檢查使用者是否存在
$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>
 在這個指令碼中,當使用者輸入正常的使用者名稱和密碼,假設值分別為 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中的註釋符, #之後的語句不被執行,實現上這行語句就成了:
  SELECT * FROM tbl_users WHERE username=`zhang3` OR 1=1
  這樣攻擊者就可以繞過認證了。如果攻擊者知道資料庫結構,那麼它構建一個 UNION SELECT,那就更危險了:
  假設在 username 中輸入:zhang3 ` OR 1 =1 UNION select cola, colb,cold FROM tbl_b #
  在password 輸入: abc123,
  則提交的 SQL 語句變成:
  SELECT * FROM tbl_users WHERE username=`zhang3 ` OR 1 =1 UNION select cola, colb,cold FROM tbl_b #` AND password = `abc123` LIMIT 0,1
  這樣就相當危險了。
  二、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>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 登入。一個正常的語句,使用者輸入 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
  攻擊者達到了目的。
  三、如何防止 PHP的SQL 注入攻擊
  如何防止 php sql 注入攻擊?我認為最重要的一點,就是要對資料型別進行檢查和轉義。總結的幾點規則如下:
  1. php.ini 中的 display_errors 選項,應該設為 display_errors = off。這樣 php 指令碼出錯之後,不會在 web 頁面輸出錯誤,以免讓攻擊者分析出有作的資訊。
  2. 呼叫 mysql_query 等 mysql 函式時,前面應該加上 @,即 @mysql_query(…),這樣 mysql 錯誤不會被輸出。同理以免讓攻擊者分析出有用的資訊。另外,有些程式設計師在做開發時,當 mysql_query出錯時,習慣輸出錯誤以及 sql 語句,例如:
<php
$t_strSQL = “SELECT a from b….”;
if (mysql_query($t_strSQL)) {
// 正確的處理
} else {
echo “錯誤! SQL 語句:$t_strSQL
錯誤資訊” . mysql_query();
exit;
}
?>
  這種做法是相當危險和愚蠢的。如果一定要這麼做,最好在網站的配置檔案中,設一個全域性變數或定義一個巨集,設一下 debug 標誌:
<?php
//全域性配置檔案中:
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錯誤資訊” . mysql_query();
}
exit;
}
?>
  3. 對提交的 sql 語句,進行轉義和型別檢查。
  四、我寫的一個安全引數獲取函式
  為了防止使用者的錯誤資料和 php + mysql 注入 ,我寫了一個函式 PAPI_GetSafeParam(),用來獲取安全的引數值:
<?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;
}
?>
  在這個函式中,有三個引數:
  $pi_strName:變數名
  $pi_Def:預設值
  $pi_iType: 資料型別。取值為 XH_PARAM_INT,XH_PARAM_TXT,分別表示數值型和文字型。
  如果請求是數值型,那麼呼叫 is_numeric() 判斷是否為數值。如果不是,則返回程式指定的預設值。
  簡單起見,對於文字串,我將使用者輸入的所有危險字元(包括HTML程式碼),全部轉義。由於 php 函式 addslashes()存在漏洞,我用 str_replace()直接替換。get_magic_quotes_gpc( ) 函式是 php 的函式,用來判斷 magic_quotes_gpc 選項是否開啟。
  剛才第二節的示例,程式碼可以這樣呼叫:
<?php
if (isset($_POST[“f_login”])) {
// 連線資料庫…
// …程式碼略…
// 檢查使用者是否存在
$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)) {
// 成功查詢之後的處理. 略…
}
}
?>
  這樣的話,就已經相當安全了。PAPI_GetSafeParam的程式碼有點長,但犧牲這點效率,對保證安全,是值得的。希望大家多批評指正。

最新內容請見作者的GitHub頁:http://qaseven.github.io/


相關文章