前言
值得慶幸的是如今開發者在構建網站時,已經開始注重安全問題了。絕大部分開發者都意識到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;
安全考慮:
過濾用戶輸入參數了嗎? — 完成檢查
使用單引號(’)來增加安全性了嗎? — 完成檢查
按理說應該不會出錯了啊?
然而,攻擊者依然能夠以任意用戶身份進行登錄!
攻擊手法
在談論這種攻擊手法之前,首先我們需要了解幾個關鍵知識點。
-
在SQL中執行字符串處理時,字符串末尾的空格符將會被刪除。換句話說“vampire”等同於“vampire ”,對於絕大多數情況來說都是成立的(諸如WHERE子句中的字符串或INSERT語句中的字符串)例如以下語句的查詢結果,與使用用戶名“vampire”進行查詢時的結果是一樣的。
SELECT * FROM users WHERE username='vampire ';
-
但也存在異常情況,最好的例子就是LIKE子句了。注意,對尾部空白符的這種修剪操作,主要是在“字符串比較”期間進行的。這是因為,SQL會在內部使用空格來填充字符串,以便在比較之前使其它們的長度保持一致。
-
在所有的INSERT查詢中,SQL都會根據varchar(n)來限制字符串的最大長度。也就是說,如果字符串的長度大於“n”個字符的話,那么僅使用字符串的前“n”個字符。比如特定列的長度約束為“5”個字符,那么在插入字符串“vampire”時,實際上只能插入字符串的前5個字符,即“vampi”。
現在,讓我們建立一個測試數據庫來演示具體攻擊過程。
vampire@linux:~$ 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上成功通過測試。我相信在其他情況下依舊適用。
防御手段
毫無疑問,在進行軟件開發時,需要對此類安全漏洞引起注意。我們可采取以下幾項措施進行防御:
-
將要求或者預期具有唯一性的那些列加上UNIQUE約束。實際上這是一個涉及軟件開發的重要規則,即使你的代碼有維持其完整性的功能,也應該恰當的定義數據。由於’username’列具有UNIQUE約束,所以不能插入另一條記錄。將會檢測到兩個相同的字符串,並且INSERT查詢將失敗。
-
最好使用’id’作為數據庫表的主鍵。並且數據應該通過程序中的id進行跟蹤
-
為了更加安全,還可以用手動調整輸入參數的限制長度(依照數據庫設置)
文章參考自:https://dhavalkapil.com/blogs/SQL-Attack-Constraint-Based/