PHP安全之?dāng)?shù)據(jù)庫(kù)安全——SQL注入及預(yù)防措施
很多 web 開(kāi)發(fā)者沒(méi)有注意到 SQL 查詢(xún)是可以被篡改的,因而把 SQL 查詢(xún)當(dāng)作可信任的命令。殊不知道,SQL 查詢(xún)可以繞開(kāi)訪(fǎng)問(wèn)控制,從而繞過(guò)身份驗(yàn)證和權(quán)限檢查。更有甚者,有可能通過(guò) SQL 查詢(xún)?nèi)ミ\(yùn)行主機(jī)操作系統(tǒng)級(jí)的命令。
直接 SQL 命令注入就是攻擊者常用的一種創(chuàng)建或修改已有 SQL 語(yǔ)句的技術(shù),從而達(dá)到取得隱藏?cái)?shù)據(jù),或覆蓋關(guān)鍵的值,甚至執(zhí)行數(shù)據(jù)庫(kù)主機(jī)操作系統(tǒng)命令的目的。這是通過(guò)應(yīng)用程序取得用戶(hù)輸入并與靜態(tài)參數(shù)組合成 SQL 查詢(xún)來(lái)實(shí)現(xiàn)的。下面將會(huì)給出一些真實(shí)的例子。
由于在缺乏對(duì)輸入的數(shù)據(jù)進(jìn)行驗(yàn)證,并且使用了超級(jí)用戶(hù)或其它有權(quán)創(chuàng)建新用戶(hù)的數(shù)據(jù)庫(kù)帳號(hào)來(lái)連接,攻擊者可以在數(shù)據(jù)庫(kù)中新建一個(gè)超級(jí)用戶(hù)。
Example #1 一段實(shí)現(xiàn)數(shù)據(jù)分頁(yè)顯示的代碼……也可以被用作創(chuàng)建一個(gè)超級(jí)用戶(hù)(PostgreSQL系統(tǒng))。
<?php $offset = $argv[0]; // 注意,沒(méi)有輸入驗(yàn)證! $query = 'SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;'; $result = pg_query($conn, $query);?>
一般的用戶(hù)會(huì)點(diǎn)擊?$offset?已被斌值的“上一頁(yè)”、“下一頁(yè)”的鏈接。原本代碼只會(huì)認(rèn)為?$offset?是一個(gè)數(shù)值。然而,如果有人嘗試把以下語(yǔ)句先經(jīng)過(guò)?urlencode()?處理,然后加入U(xiǎn)RL中的話(huà):
0;insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd) select ’crack’, usesysid, ’t’,’t’,’crack’ from pg_shadow where usename=’postgres’;--
那么他就可以創(chuàng)建一個(gè)超級(jí)用戶(hù)了。注意那個(gè)?0;?只不過(guò)是為了提供一個(gè)正確的偏移量以便補(bǔ)充完整原來(lái)的查詢(xún),使它不要出錯(cuò)而已。
Note:
--?是 SQL 的注釋標(biāo)記,一般可以使用來(lái)它告訴 SQL 解釋器忽略后面的語(yǔ)句。
對(duì)顯示搜索結(jié)果的頁(yè)面下手是一個(gè)能得到密碼的可行辦法。攻擊者所要做的只不過(guò)是找出哪些提交上去的變量是用于 SQL 語(yǔ)句并且處理不當(dāng)?shù)摹6@類(lèi)的變量通常都被用于SELECT?查詢(xún)中的條件語(yǔ)句,如?WHERE, ORDER BY, LIMIT?和?OFFSET。如果數(shù)據(jù)庫(kù)支持?UNION?構(gòu)造的話(huà),攻擊者還可能會(huì)把一個(gè)完整的 SQL 查詢(xún)附加到原來(lái)的語(yǔ)句上以便從任意數(shù)據(jù)表中得到密碼。因此,對(duì)密碼字段加密是很重要的。
Example #2 顯示文章……以及一些密碼(任何數(shù)據(jù)庫(kù)系統(tǒng))
<?php $query = 'SELECT id, name, inserted, size FROM productsWHERE size = ’$size’ORDER BY $order LIMIT $limit, $offset;'; $result = odbc_exec($conn, $query);?>
可以在原來(lái)的查詢(xún)的基礎(chǔ)上添加另一個(gè)?SELECT?查詢(xún)來(lái)獲得密碼:
’union select ’1’, concat(uname||’-’||passwd) as name, ’1971-01-01’, ’0’ from usertable;--
假如上述語(yǔ)句(使用?’?和?--)被加入到?$query?中的任意一個(gè)變量的話(huà),那么就麻煩了。
SQL 中的 UPDATE 也會(huì)受到攻擊。這種查詢(xún)也可能像上面的例子那樣被插入或附加上另一個(gè)完整的請(qǐng)求。但是攻擊者更愿意對(duì)?SET?子句下手,這樣他們就可以更改數(shù)據(jù)表中的一些數(shù)據(jù)。這種情況下必須要知道數(shù)據(jù)庫(kù)的結(jié)構(gòu)才能修改查詢(xún)成功進(jìn)行。可以通過(guò)表單上的變量名對(duì)字段進(jìn)行猜測(cè),或者進(jìn)行暴力破解。對(duì)于存放用戶(hù)名和密碼的字段,命名的方法并不多。
Example #3 從重設(shè)密碼……到獲得更多權(quán)限(任何數(shù)據(jù)庫(kù)系統(tǒng))
<?php $query = 'UPDATE usertable SET pwd=’$pwd’ WHERE uid=’$uid’;';?>
但是惡意的用戶(hù)會(huì)把?’ or uid like’%admin%’; --?作為變量的值提交給?$uid?來(lái)改變 admin 的密碼,或者把?$pwd?的值提交為?'hehehe’, admin=’yes’, trusted=100 '(后面有個(gè)空格)去獲得更多的權(quán)限。這樣做的話(huà),查詢(xún)語(yǔ)句實(shí)際上就變成了:
<?php // $uid == ’ or uid like’%admin%’; -- $query = 'UPDATE usertable SET pwd=’...’ WHERE uid=’’ or uid like ’%admin%’; --'; // $pwd == 'hehehe’, admin=’yes’, trusted=100 ' $query = 'UPDATE usertable SET pwd=’hehehe’, admin=’yes’, trusted=100 WHERE...;';?>
下面這個(gè)可怕的例子將會(huì)演示如何在某些數(shù)據(jù)庫(kù)上執(zhí)行系統(tǒng)命令。
Example #4 攻擊數(shù)據(jù)庫(kù)所在主機(jī)的操作系統(tǒng)(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);?>
MSSQL 服務(wù)器會(huì)執(zhí)行這條 SQL 語(yǔ)句,包括它后面那個(gè)用于向系統(tǒng)添加用戶(hù)的命令。如果這個(gè)程序是以?sa?運(yùn)行而 MSSQLSERVER 服務(wù)又有足夠的權(quán)限的話(huà),攻擊者就可以獲得一個(gè)系統(tǒng)帳號(hào)來(lái)訪(fǎng)問(wèn)主機(jī)了。
Note:
雖然以上的例子是針對(duì)某一特定的數(shù)據(jù)庫(kù)系統(tǒng)的,但是這并不代表不能對(duì)其它數(shù)據(jù)庫(kù)系統(tǒng)實(shí)施類(lèi)似的攻擊。使用不同的方法,各種數(shù)據(jù)庫(kù)都有可能遭殃。
預(yù)防措施也許有人會(huì)自我安慰,說(shuō)攻擊者要知道數(shù)據(jù)庫(kù)結(jié)構(gòu)的信息才能實(shí)施上面的攻擊。沒(méi)錯(cuò),確實(shí)如此。但沒(méi)人能保證攻擊者一定得不到這些信息,一但他們得到了,數(shù)據(jù)庫(kù)有泄露的危險(xiǎn)。如果你在用開(kāi)放源代碼的軟件包來(lái)訪(fǎng)問(wèn)數(shù)據(jù)庫(kù),比如論壇程序,攻擊者就很容得到到相關(guān)的代碼。如果這些代碼設(shè)計(jì)不良的話(huà),風(fēng)險(xiǎn)就更大了。
這些攻擊總是建立在發(fā)掘安全意識(shí)不強(qiáng)的代碼上的。所以,永遠(yuǎn)不要信任外界輸入的數(shù)據(jù),特別是來(lái)自于客戶(hù)端的,包括選擇框、表單隱藏域和 cookie。就如上面的第一個(gè)例子那樣,就算是正常的查詢(xún)也有可能造成災(zāi)難。
永遠(yuǎn)不要使用超級(jí)用戶(hù)或所有者帳號(hào)去連接數(shù)據(jù)庫(kù)。要用權(quán)限被嚴(yán)格限制的帳號(hào)。檢查輸入的數(shù)據(jù)是否具有所期望的數(shù)據(jù)格式。PHP 有很多可以用于檢查輸入的函數(shù),從簡(jiǎn)單的變量函數(shù)和字符類(lèi)型函數(shù)(比如?is_numeric(),?ctype_digit())到復(fù)雜的Perl 兼容正則表達(dá)式函數(shù)都可以完成這個(gè)工作。如果程序等待輸入一個(gè)數(shù)字,可以考慮使用?is_numeric()?來(lái)檢查,或者直接使用?settype()?來(lái)轉(zhuǎn)換它的類(lèi)型,也可以用?sprintf()?把它格式化為數(shù)字。Example #5 一個(gè)實(shí)現(xiàn)分頁(yè)更安全的方法
<?php settype($offset, ’integer’); $query = 'SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;'; // 請(qǐng)注意格式字符串中的 %d,如果用 %s 就毫無(wú)意義了 $query = sprintf('SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;',$offset);?>使用數(shù)據(jù)庫(kù)特定的敏感字符轉(zhuǎn)義函數(shù)(比如?mysql_escape_string()?和?sql_escape_string())把用戶(hù)提交上來(lái)的非數(shù)字?jǐn)?shù)據(jù)進(jìn)行轉(zhuǎn)義。如果數(shù)據(jù)庫(kù)沒(méi)有專(zhuān)門(mén)的敏感字符轉(zhuǎn)義功能的話(huà)?addslashes()?和?str_replace()?可以代替完成這個(gè)工作。看看第一個(gè)例子,此例顯示僅在查詢(xún)的靜態(tài)部分加上引號(hào)是不夠的,查詢(xún)很容易被攻破。要不擇手段避免顯示出任何有關(guān)數(shù)據(jù)庫(kù)的信心,尤其是數(shù)據(jù)庫(kù)結(jié)構(gòu)。也可以選擇使用數(shù)據(jù)庫(kù)的存儲(chǔ)過(guò)程和預(yù)定義指針等特性來(lái)抽象數(shù)庫(kù)訪(fǎng)問(wèn),使用戶(hù)不能直接訪(fǎng)問(wèn)數(shù)據(jù)表和視圖。但這個(gè)辦法又有別的影響。
除此之外,在允許的情況下,使用代碼或數(shù)據(jù)庫(kù)系統(tǒng)保存查詢(xún)?nèi)罩疽彩且粋€(gè)好辦法。顯然,日志并不能防止任何攻擊,但利用它可以跟蹤到哪個(gè)程序曾經(jīng)被嘗試攻擊過(guò)。日志本身沒(méi)用,要查閱其中包含的信息才行。畢竟,更多的信息總比沒(méi)有要好。
相關(guān)文章:
1. Python多線(xiàn)程操作之互斥鎖、遞歸鎖、信號(hào)量、事件實(shí)例詳解2. Python常用GUI框架原理解析匯總3. XML入門(mén)的常見(jiàn)問(wèn)題(一)4. Django程序的優(yōu)化技巧5. Jsp中request的3個(gè)基礎(chǔ)實(shí)踐6. idea給項(xiàng)目打war包的方法步驟7. jsp EL表達(dá)式詳解8. 怎樣才能用js生成xmldom對(duì)象,并且在firefox中也實(shí)現(xiàn)xml數(shù)據(jù)島?9. IntelliJ IDEA 統(tǒng)一設(shè)置編碼為utf-8編碼的實(shí)現(xiàn)10. idea設(shè)置自動(dòng)導(dǎo)入依賴(lài)的方法步驟
