很多 web 開(kāi)發(fā)者沒(méi)有注意到 SQL 查詢(xún)是可以被篡改的,因而把 SQL 查詢(xún)當作可信任的命令。殊不知道,SQL 查詢(xún)可以繞開(kāi)訪(fǎng)問(wèn)控制,從而繞過(guò)身份驗證和權限檢查。更有甚者,有可能通過(guò) SQL 查詢(xún)去運行主機操作系統級的命令。
直接 SQL 命令注入就是攻擊者常用的一種創(chuàng )建或修改已有 SQL 語(yǔ)句的技術(shù),從而達到取得隱藏數據,或覆蓋關(guān)鍵的值,甚至執行數據庫主機操作系統命令的目的。這是通過(guò)應用程序取得用戶(hù)輸入并與靜態(tài)參數組合成 SQL 查詢(xún)來(lái)實(shí)現的。下面將會(huì )給出一些真實(shí)的例子。
由于在缺乏對輸入的數據進(jìn)行驗證,并且使用了超級用戶(hù)或其它有權創(chuàng )建新用戶(hù)的數據庫帳號來(lái)連接,攻擊者可以在數據庫中新建一個(gè)超級用戶(hù)。
示例 #1 一段實(shí)現數據分頁(yè)顯示的代碼…… 也可以被用作創(chuàng )建一個(gè)超級用戶(hù)(PostgreSQL 數據庫)。
<?php
$offset = $argv[0]; // 注意,沒(méi)有輸入驗證!
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
$result = pg_query($conn, $query);
?>
0; insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd) select 'crack', usesysid, 't','t','crack' from pg_shadow where usename='postgres'; --
0;
只不過(guò)是為了提供一個(gè)正確的偏移量以便補充完整原來(lái)的查詢(xún),使它不要出錯而已。
注意:
--
是 SQL 的注釋標記,一般可以使用來(lái)它告訴 SQL 解釋器忽略后面的語(yǔ)句。
對顯示搜索結果的頁(yè)面下手是一個(gè)能得到密碼的可行辦法。攻擊者所要做的只不過(guò)是找出哪些提交上去的變量是用于
SQL 語(yǔ)句并且處理不當的。而這類(lèi)的變量通常都被用于 SELECT
查詢(xún)中的條件語(yǔ)句,如 WHERE, ORDER BY, LIMIT
和
OFFSET
。如果數據庫支持 UNION
構造的話(huà),攻擊者還可能會(huì )把一個(gè)完整的 SQL
查詢(xún)附加到原來(lái)的語(yǔ)句上以便從任意數據表中得到密碼。因此,對密碼字段加密是很重要的。
示例 #2 顯示文章……以及一些密碼(任何數據庫系統)
<?php
$query = "SELECT id, name, inserted, size FROM products
WHERE size = '$size'";
$result = odbc_exec($conn, $query);
?>
SELECT
查詢(xún)來(lái)獲得密碼:
' union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable; --
'
和
--
)被加入到 $query
中的任意一個(gè)變量的話(huà),那么就麻煩了。
SQL 中的 UPDATE
也會(huì )受到攻擊。這種查詢(xún)也可能像上面的例子那樣被插入或附加上另一個(gè)完整的請求。但是攻擊者更愿意對
SET
子句下手,這樣他們就可以更改數據表中的一些數據。這種情況下必須要知道數據庫的結構才能修改查詢(xún)成功進(jìn)行??梢酝ㄟ^(guò)表單上的變量名對字段進(jìn)行猜測,或者進(jìn)行暴力破解。對于存放用戶(hù)名和密碼的字段,命名的方法并不多。
示例 #3 從重設密碼……到獲得更多權限(任何數據庫系統)
<?php
$query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';";
?>
' or uid like'%admin%'; --
作為變量的值提交給 $uid 來(lái)改變 admin 的密碼,或者把
$pwd 的值提交為 "hehehe', admin='yes',
trusted=100 "
(后面有個(gè)空格)去獲得更多的權限。這樣做的話(huà),查詢(xún)語(yǔ)句實(shí)際上就變成了:
<?php
// $uid: ' or uid like '%admin%
$query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%';";
// $pwd: hehehe', trusted=100, admin='yes
$query = "UPDATE usertable SET pwd='hehehe', trusted=100, admin='yes' WHERE
...;";
?>
下面這個(gè)可怕的例子將會(huì )演示如何在某些數據庫上執行系統命令。
示例 #4 攻擊數據庫所在主機的操作系統(MSSQL Server 數據庫)
<?php
$query = "SELECT * FROM products WHERE id LIKE '%$prod%'";
$result = mssql_query($query);
?>
a%' exec master..xp_cmdshell 'net user test testpass /ADD' --
作為變量 $prod的值,那么
$query 將會(huì )變成
<?php
$query = "SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD' --%'";
$result = mssql_query($query);
?>
sa
運行而 MSSQLSERVER
服務(wù)又有足夠的權限的話(huà),攻擊者就可以獲得一個(gè)系統帳號來(lái)訪(fǎng)問(wèn)主機了。
注意:
雖然以上的例子是針對某一特定的數據庫系統的,但是這并不代表不能對其它數據庫系統實(shí)施類(lèi)似的攻擊。使用不同的方法,各種數據庫都有可能遭殃。
也許有人會(huì )自我安慰,說(shuō)攻擊者要知道數據庫結構的信息才能實(shí)施上面的攻擊。沒(méi)錯,確實(shí)如此。但沒(méi)人能保證攻擊者一定得不到這些信息,一但他們得到了,數據庫有泄露的危險。如果你在用開(kāi)放源代碼的軟件包來(lái)訪(fǎng)問(wèn)數據庫,比如論壇程序,攻擊者就很容得到到相關(guān)的代碼。如果這些代碼設計不良的話(huà),風(fēng)險就更大了。
這些攻擊總是建立在發(fā)掘安全意識不強的代碼上的。所以,永遠不要信任外界輸入的數據,特別是來(lái)自于客戶(hù)端的,包括選擇框、表單隱藏域和 cookie。就如上面的第一個(gè)例子那樣,就算是正常的查詢(xún)也有可能造成災難。
如果程序等待輸入一個(gè)數字,可以考慮使用 is_numeric() 來(lái)檢查,或者直接使用 settype() 來(lái)轉換它的類(lèi)型,也可以用 sprintf() 把它格式化為數字。
示例 #5 一個(gè)實(shí)現分頁(yè)更安全的方法
<?php
settype($offset, 'integer');
$query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;";
// 請注意格式字符串中的 %d,如果用 %s 就毫無(wú)意義了
$query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;",
$offset);
?>
除此之外,在允許的情況下,使用代碼或數據庫系統保存查詢(xún)日志也是一個(gè)好辦法。顯然,日志并不能防止任何攻擊,但利用它可以跟蹤到哪個(gè)程序曾經(jīng)被嘗試攻擊過(guò)。日志本身沒(méi)用,要查閱其中包含的信息才行。畢竟,更多的信息總比沒(méi)有要好。