注:以下內(nèi)容轉(zhuǎn)載自:https://blog.csdn.net/wy_97/article/details/77972375
https://www.bilibili.com/watchlater/#/BV1Pf4y1U7Dw/p1
背景介紹
最近,我遇到了一個有趣的代碼片段,開發(fā)者嘗試各種方法來確保數(shù)據(jù)庫的安全訪問。當新用戶嘗試注冊時,將運行以下代碼:
<?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;
安全考慮:
- 過濾用戶輸入?yún)?shù)了嗎? — 完成檢查
- 使用單引號(’)來增加安全性了嗎? — 完成檢查
按理說應該不會出錯了???
然而,攻擊者依然能夠以任意用戶身份進行登錄!
攻擊手法:
在談論這種攻擊手法之前,首先我們需要了解幾個關鍵知識點。
- 在SQL中執(zhí)行字符串處理時,字符串末尾的空格符將會被刪除。換句話說“vampire”等同于“vampire ???”,對于絕大多數(shù)情況來說都是成立的(諸如WHERE子句中的字符串或INSERT語句中的字符串)例如以下語句的查詢結(jié)果,與使用用戶名“vampire”進行查詢時的結(jié)果是一樣的。
但也存在異常情況,最好的例子就是LIKE子句了。注意,對尾部空白符的這種修剪操作,主要是在“字符串比較”期間進行的。這是因為,SQL會在內(nèi)部使用空格來填充字符串,以便在比較之前使其它們的長度保持一致。SELECT * FROM users WHERE username='vampire ';
- 在所有的INSERT查詢中,SQL都會根據(jù)varchar(n)來限制字符串的最大長度。也就是說,如果字符串的長度大于“n”個字符的話,那么僅使用字符串的前“n”個字符。比如特定列的長度約束為“5”個字符,那么在插入字符串“vampire”時,實際上只能插入字符串的前5個字符,即“vampi”。
現(xiàn)在,讓我們建立一個測試數(shù)據(jù)庫來演示具體攻擊過程。
接著創(chuàng)建一個數(shù)據(jù)表users,其包含username和password列,并且字段的最大長度限制為25個字符。然后,我將向username字段插入“vampire”,向password字段插入“my_password”。vampire@linux:~$ mysql -u root -p mysql> CREATE DATABASE testing; Query OK, 1 row affected (0.03 sec) mysql> USE testing; Database changed
為了展示尾部空白字符的修剪情況,我們可以鍵入下列命令: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)
現(xiàn)在我們假設一個存在漏洞的網(wǎng)站使用了前面提到的PHP代碼來處理用戶的注冊及登錄過程。為了侵入任意用戶的帳戶(在本例中為“vampire”),只需要使用用戶名“vampire[許多空白符]1”和一個隨機密碼進行注冊即可。對于選擇的用戶名,前25個字符應該只包含vampire和空白字符,這樣做將有助于繞過檢查特定用戶名是否已存在的查詢。mysql> SELECT * FROM users -> WHERE username='vampire '; +----------+-------------+ | username | password | +----------+-------------+ | vampire | my_password | +----------+-------------+ 1 row in set (0.00 sec)
需要注意的是,在執(zhí)行SELECT查詢語句時,SQL是不會將字符串縮短為25個字符的。因此,這里將使用完整的字符串進行搜索,所以不會找到匹配的結(jié)果。接下來,當執(zhí)行INSERT查詢語句時,它只會插入前25個字符。mysql> SELECT * FROM users WHERE username='vampire 1'; Empty set (0.00 sec)
很好,現(xiàn)在我們檢索“vampire”的,將返回兩個獨立用戶。注意,第二個用戶名實際上是“vampire”加上尾部的18個空格?,F(xiàn)在,如果使用用戶名“vampire”和密碼“random_pass”登錄的話,則所有搜索該用戶名的SELECT查詢都將返回第一個數(shù)據(jù)記錄,也就是原始的數(shù)據(jù)記錄。這樣的話,攻擊者就能夠以原始用戶身份登錄。這個攻擊已經(jīng)在MySQL和SQLite上成功通過測試。我相信在其他情況下依舊適用。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)
防御手段
毫無疑問,在進行軟件開發(fā)時,需要對此類安全漏洞引起注意。我們可采取以下幾項措施進行防御:
- 將要求或者預期具有唯一性的那些列加上UNIQUE約束。實際上這是一個涉及軟件開發(fā)的重要規(guī)則,即使你的代碼有維持其完整性的功能,也應該恰當?shù)亩x數(shù)據(jù)。由于’username’列具有UNIQUE約束,所以不能插入另一條記錄。將會檢測到兩個相同的字符串,并且INSERT查詢將失敗。
- 最好使用’id’作為數(shù)據(jù)庫表的主鍵。并且數(shù)據(jù)應該通過程序中的id進行跟蹤
- 為了更加安全,還可以用手動調(diào)整輸入?yún)?shù)的限制長度(依照數(shù)據(jù)庫設置)