1. 程式人生 > >SQL注入原理及防範

SQL注入原理及防範

    前段時間部門遇到SQL注入攻擊,在此,我也分享一下自己的經驗和理解。

    首先一個很重要的論點:SQL注入是可以完全杜絕的

SQL注入原因

    通俗點講,SQL注入的根本原因是: "使用者輸入資料"意外變成了程式碼被執行。

    "使用者輸入資料"我這裡可以指Web前端$_POST,$_GET獲取的資料,也可以指從資料庫獲取的資料,當然也不排除程式猿無意中使用的特殊字串。

    在SQL語句的拼接中,一些含特殊字元的變數在拼接時破壞了SQL語句的結構,導致"使用者輸入資料"意外變成了程式碼被執行。

SQL語句拼接

    以PHP語言為例,PHP中SQL拼接方式主要有以下幾種

1.  數值型別 直接拼接

例如
$sql = 'SELECT * from table where id = '. $id
                 $sql = "SELECT * from table where id = $id ";

這裡開發者預設將$id當做數值型別,進行直接拼接。除錯時,當$id為字串時會報錯,所以可以提前發現拼接錯誤。通常會用intval($id),floatval($id)強制轉換成數值型,並不會造成SQL注入。

2.  字串型拼接

例如
$sql = 'SELECT * from table where name = '."$name"
        $sql = "SELECT * from table where name = '$name' ";

這裡開發者預設將$name當做字串處理。當$name中含有單引號(')或 雙引號("),會抵消掉$name變數上的單引號或雙引號,從而破壞了$sql的結構使得$name有可能變成SQL命令被執行,這也是為什麼一些字元轉義函式如addslashes()可以防止簡單的SQL注入。

除了$name直接含有單/雙引號('/")外,由於一些特殊的編碼(如特定的漢子字元)也可以抵消$name變數上的單引號或雙引號,所以addslashes()、mysql_escape_string()、mysql_real_escape_string() 這種轉義特殊字元的方法是有風險的。

但無論編碼情況多麼複雜,SQL注入有一點是不變的:  抵消 單/雙引號,從而破壞SQL語句結構。

理解這一點,可以指導開發者編碼,從而加強開發者對SQL注入風險的控制能力。

防止SQL注入方法

      在此我把方法歸納為3類。

    1.字元(串)過濾法  (個人認為是網上的一種偽方法,注入後減小損失,以自我安慰,其實已經注入了)

 通常過濾字串變數中一些敏感詞和字元,如" '|and|exec|insert|select|delete|update|count|*|%|chr|mid|master|truncate|char|declare|;|or|-|+|, "等,或者用正則表示式過濾傳入的引數,類似的方法網上有較多參考。

       但是個人覺得字元(串)過濾法並不適合防止SQL注入,因為字元(串)過濾對防止破壞SQL語句結構毫無用處(過濾單/雙引號除外),字元(串)過濾法更側重SQL語句結構破壞後,阻止侵入者執行他的程式碼意圖。(而實際上自己的SQL語句結構已經被破壞了)。

   個人認為這是一種偽方法,是把字元過濾和防止SQL注入兩個概念混淆。字串過濾更是業務層面上的要求,過濾使用者輸入的'insert ,and,delete'等關鍵詞是否符合業務需是需要考慮的。 

    2.字元轉義法  (不推薦使用,無法避免編碼注入風險)

       addslashes()    (不推薦使用,只能防止 單/雙 引號注入)

         addslashes()是在 單引號(')、雙引號(")、反斜線(\)與 NUL(NULL 字元)前加上了反斜線,但是遇到一些特殊編碼也無能為力。如:含0xbf27 的字就不會,所以 select * from table where name='縗' OR 1 就會產生SQL注入。在上面的SQL拼接中 $sql = " SELECT * from table where name = '$name' "中,如果$name = "'縗' or 1", 用$name = addslashes($name) 還是會產生SQL注入.

    mysql_escape_string()、mysql_real_escape_string()   (不推薦使用,無法避免編碼注入風險,且只適用於MySQL)

        mysql_escape_string 和 mysql_real_escape_string功能一樣,都是在以下字元前新增反斜槓: \x00, \n, \r, \, ', " 和 \x1a。mysql_real_escape_string使用之前要先連線上資料庫,會考慮當前連結connection字符集。

      字元轉義法並不能完全避免SQL注入風險,應謹慎使用。

      由於一些複雜的業務邏輯,無法完全避免SQL語句拼接,在變數可控的情況下,使用這種字元轉義法也未嘗不可,比不使用直接拼接要強得多。

    3.預處理語句法  (解析協議層面上完全規避SQL注入)

         PDO

$pdo = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');  
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);  
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);  
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');  
$stmt->execute(array('name' => $name));  
foreach ($stmt as $row) {
}

         MYSQLi

$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
$stmt->bind_param('s', $name);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
}

      預處理語句方法會先解析SQL語句,然後通過傳入不同的引數值來執行SQL,不需要每次執行都解析SQL語句,查詢執行路徑比常規查詢短,所以預處理語句方法效率更高;預處理語句在SQL語句解析協議上避免將引數當做SQL命令執行,僅僅當做值傳遞,所以可以完全避免SQL注入。

    總之,預處理語句方法可以完全避免SQL注入,並且效率更高。