1. 程式人生 > >基於約束的SQL攻擊

基於約束的SQL攻擊

前言

值得慶幸的是如今開發者在構建網站時,已經開始注重安全問題了。絕大部分開發者都意識到SQL注入漏洞的存在,在本文我想與讀者共同去探討另一種與SQL資料庫相關的漏洞,其危害與SQL注入不相上下,但卻不太常見。接下來,我將為讀者詳細展示這種攻擊手法,以及相應的防禦策略。

注意:本文不是講述SQL注入攻擊

背景介紹

最近,我遇到了一個有趣的程式碼片段,開發者嘗試各種方法來確保資料庫的安全訪問。當新使用者嘗試註冊時,將執行以下程式碼:

<?php
// Checking whether a user with the same username exists
$username = mysql_real_escape_string($_GET['username']);
$password = mysql_real_escape_string($_GET['password']);
$query = "SELECT *
          FROM users
          WHERE username='$username'";
$res = mysql_query($query, $database);
if($res) {
  if(mysql_num_rows($res) > 0) {
    // User exists, exit gracefully
    .
    .
  }
  else {
    // If not, only then insert a new entry
    $query = "INSERT INTO users(username, password)
              VALUES ('$username','$password')";
    .
    .
  }
}

使用以下程式碼驗證登入資訊:

<?php
$username = mysql_real_escape_string($_GET['username']);
$password = mysql_real_escape_string($_GET['password']);
$query = "SELECT username FROM users
          WHERE username='$username'
              AND password='$password' ";
$res = mysql_query($query, $database);
if($res) {
  if(mysql_num_rows($res) > 0){
      $row = mysql_fetch_assoc($res);
      return $row['username'];
  }
}
return Null;

安全考慮:

  • 過濾使用者輸入引數了嗎? — 完成檢查

  • 使用單引號(’)來增加安全性了嗎? — 完成檢查

按理說應該不會出錯了啊?

然而,攻擊者依然能夠以任意使用者身份進行登入!

攻擊手法

在談論這種攻擊手法之前,首先我們需要了解幾個關鍵知識點。

  1. 在SQL中執行字串處理時,字串末尾的空格符將會被刪除。換句話說“vampire”等同於“vampire ”,對於絕大多數情況來說都是成立的(諸如WHERE子句中的字串或INSERT語句中的字串)例如以下語句的查詢結果,與使用使用者名稱“vampire”進行查詢時的結果是一樣的。

    SELECT * FROM users WHERE username='vampire     ';
    

    但也存在異常情況,最好的例子就是LIKE子句了。注意,對尾部空白符的這種修剪操作,主要是在“字串比較”期間進行的。這是因為,SQL會在內部使用空格來填充字串,以便在比較之前使其它們的長度保持一致。

  2. 在所有的INSERT查詢中,SQL都會根據varchar(n)來限制字串的最大長度。也就是說,如果字串的長度大於“n”個字元的話,那麼僅使用字串的前“n”個字元。比如特定列的長度約束為“5”個字元,那麼在插入字串“vampire”時,實際上只能插入字串的前5個字元,即“vampi”。

現在,讓我們建立一個測試資料庫來演示具體攻擊過程。

[email protected]:~$ mysql -u root -p
mysql> CREATE DATABASE testing;
Query OK, 1 row affected (0.03 sec)
mysql> USE testing;
Database changed

接著建立一個數據表users,其包含username和password列,並且欄位的最大長度限制為25個字元。然後,我將向username欄位插入“vampire”,向password欄位插入“my_password”。

mysql> CREATE TABLE users (
    ->   username varchar(25),
    ->   password varchar(25)
    -> );
Query OK, 0 rows affected (0.09 sec)
mysql> INSERT INTO users
    -> VALUES('vampire', 'my_password');
Query OK, 1 row affected (0.11 sec)
mysql> SELECT * FROM users;
+----------+-------------+
| username | password    |
+----------+-------------+
| vampire  | my_password |
+----------+-------------+
1 row in set (0.00 sec)

為了展示尾部空白字元的修剪情況,我們可以鍵入下列命令:

mysql> SELECT * FROM users
    -> WHERE username='vampire       ';
+----------+-------------+
| username | password    |
+----------+-------------+
| vampire  | my_password |
+----------+-------------+
1 row in set (0.00 sec)

現在我們假設一個存在漏洞的網站使用了前面提到的PHP程式碼來處理使用者的註冊及登入過程。為了侵入任意使用者的帳戶(在本例中為“vampire”),只需要使用使用者名稱“vampire[許多空白符]1”和一個隨機密碼進行註冊即可。對於選擇的使用者名稱,前25個字元應該只包含vampire和空白字元,這樣做將有助於繞過檢查特定使用者名稱是否已存在的查詢。

mysql> SELECT * FROM users
    -> WHERE username='vampire                   1';
Empty set (0.00 sec)

需要注意的是,在執行SELECT查詢語句時,SQL是不會將字串縮短為25個字元的。因此,這裡將使用完整的字串進行搜尋,所以不會找到匹配的結果。接下來,當執行INSERT查詢語句時,它只會插入前25個字元。

mysql>   INSERT INTO users(username, password)
    -> VALUES ('vampire                   1', 'random_pass');
Query OK, 1 row affected, 1 warning (0.05 sec)
mysql> SELECT * FROM users
    -> WHERE username='vampire';
+---------------------------+-------------+
| username                  | password    |
+---------------------------+-------------+
| vampire                   | my_password |
| vampire                   | random_pass |
+---------------------------+-------------+
2 rows in set (0.00 sec)

很好,現在我們檢索“vampire”的,將返回兩個獨立使用者。注意,第二個使用者名稱實際上是“vampire”加上尾部的18個空格。現在,如果使用使用者名稱“vampire”和密碼“random_pass”登入的話,則所有搜尋該使用者名稱的SELECT查詢都將返回第一個資料記錄,也就是原始的資料記錄。這樣的話,攻擊者就能夠以原始使用者身份登入。這個攻擊已經在MySQL和SQLite上成功通過測試。我相信在其他情況下依舊適用。

防禦手段

毫無疑問,在進行軟體開發時,需要對此類安全漏洞引起注意。我們可採取以下幾項措施進行防禦:

  1. 將要求或者預期具有唯一性的那些列加上UNIQUE約束。實際上這是一個涉及軟體開發的重要規則,即使你的程式碼有維持其完整性的功能,也應該恰當的定義資料。由於’username’列具有UNIQUE約束,所以不能插入另一條記錄。將會檢測到兩個相同的字串,並且INSERT查詢將失敗。

  2. 最好使用’id’作為資料庫表的主鍵。並且資料應該通過程式中的id進行跟蹤