韓林利,魏 寧
(1.盱眙縣生產(chǎn)力促進中心,江蘇 盱眙 211700;2.淮安市科技局,江蘇 淮安 223001)
PHP全稱為Hypertext Preprocessor,可以比CGI或者Perl更快速地執(zhí)行動態(tài)網(wǎng)頁,用PHP做出的動態(tài)頁面與其他的編程語言相比,PHP是將程序嵌入到HTML文檔中去執(zhí)行,執(zhí)行效率比完全生成HTML標(biāo)記的CGI要高許多;PHP還可以執(zhí)行編譯后的代碼,可以實現(xiàn)加密和優(yōu)化代碼運行的功能,使代碼運行速度更快[1-2]。由于PHP程序編寫簡單,實用性強,消耗系統(tǒng)資源少,運行快,還具有較好的跨平臺性,而且是免費的,因此PHP被廣泛應(yīng)用于網(wǎng)站的開發(fā),特別適用于中小型網(wǎng)站的快速開發(fā)。雖然PHP的安全性較好,但還是存在SQL注入攻擊等安全漏洞[3]。
服務(wù)器和PHP的運行環(huán)境配置好后,并不意味著網(wǎng)絡(luò)應(yīng)用就安全了,程序員的安全意識也起著決定性的作用。如果在網(wǎng)絡(luò)應(yīng)用的開發(fā)階段不注重網(wǎng)絡(luò)應(yīng)用的安全性,將為后期的網(wǎng)絡(luò)安全維護帶來巨大負擔(dān),例如在開發(fā)階段對數(shù)據(jù)庫的選擇操作使用”SELECT * FROM Tablename”與”SELECT colum1,colum2 FROM Tablename”相比后者在遭受SQL SElECT攻擊時泄露的信息要少的多。SQL注入的產(chǎn)生的主要原因是在網(wǎng)站開發(fā)初期程序員在編寫代碼的時候沒有對用戶輸入數(shù)據(jù)的合法性進行判斷,不僅包含用戶輸入數(shù)據(jù)在邏輯意義上的合法性,還包含用戶輸入數(shù)據(jù)是否含有數(shù)據(jù)庫查詢代碼[4-6]。SQL注入的原理是網(wǎng)絡(luò)應(yīng)用系統(tǒng)在處理用戶提交的表單信息時沒有檢查其合法性,用戶在表單中填入SQL可執(zhí)行的查詢語句欺騙服務(wù)器執(zhí)行惡意的SQL命令,網(wǎng)絡(luò)應(yīng)用程序在將表單信息發(fā)送到數(shù)據(jù)庫存儲時,數(shù)據(jù)庫識別表中的SQL語句為可執(zhí)行語句而執(zhí)行并返回攻擊者預(yù)期結(jié)果。這就是所謂的SQL Injection,即SQL注入[7-10]。
SQL注入攻擊的主要方式是構(gòu)造巧妙的SQL語句,和網(wǎng)頁提交的內(nèi)容結(jié)合起來進行注入攻擊。比較常用的方法有使用注釋符號和恒等式、使用union語句進行聯(lián)合查詢、使用insert或update語句插入或修改數(shù)據(jù)等。
(1)經(jīng)典的'or 1=1' 注入
'or 1=1'注入是非常經(jīng)典的注入語句,一般用在登錄系統(tǒng)時繞過密碼驗證,以任意用戶名登入。其原理是在程序員編寫驗證程序的時候不需驗證用戶的輸入是否含有非預(yù)期的字符串,直接傳遞給mysql_query()函數(shù)執(zhí)行,'or 1=1'使得無論密碼是否匹配,都能保證驗證語句為真,達到繞過密碼驗證的目的。
正常語句:
SELECT * FROM user WHERE username = '$username' AND pwd = '$password'
注入語句:
SELECT * FROM user WHERE username = 'tom' AND pwd = ' ' or '1=1'
其中user是表名,包含有id、username、pwd和level等字段,分別表示了用戶ID、用戶名、密碼和權(quán)限等級。這條注入語句利用了'1=1'這個恒等式作為邏輯判斷,使得即使后面的pwd判斷為假,該語句依然能夠得到正確的執(zhí)行,攻擊者不需使用密碼也能成功登錄。
例如,訪問http://admin.ev123.com/login.php,在用戶名一欄填入admin' or ' 1'=' 1,密碼為任意值,填入正確的驗證碼便可登錄成功。測試網(wǎng)站成功登陸的界面分別如圖1和圖2所示。
圖1 登陸界面
(2)利用UNION語句的注入
UNION語句注入式利用其語句特性,是程序默認的語句,在執(zhí)行后將結(jié)果集與攻擊者給定的SQL語句執(zhí)行結(jié)果集相合并到一起返回給攻擊者,達到注入的目的。
圖2 登錄成功界面
該攻擊方法的一般步驟是首先選取攻擊頁面,從整體網(wǎng)站的結(jié)構(gòu)角度進行猜測,或通過多個功能頁面對比出URL,或在表單中提交字段的意義,隨后在提交關(guān)鍵字中插入構(gòu)造好的SQL注入語句。一般情況下注入的步驟是:首先通過注入語句獲取服務(wù)器中的數(shù)據(jù)庫名;然后通過注入語句獲取數(shù)據(jù)庫中表名,在通過注入語句獲取數(shù)據(jù)表中字段名;最后獲取數(shù)據(jù)庫字段中的內(nèi)容。由于注入語句執(zhí)行的結(jié)果需要依靠系統(tǒng)既定的頁面顯示,所以注入語句獲取的信息內(nèi)容需要結(jié)合頁面顯示的框架靈活調(diào)整。例如,正常訪問網(wǎng)址http://127.0.0.1:8081/search.php?year=2014,該頁面可以顯示出數(shù)據(jù)庫中的出生年是2014年的所有用戶信息,其中year=2014的部分通過開發(fā)過程中常見的傳值方式可以猜測出,在數(shù)據(jù)庫的后臺應(yīng)該含有類似SELECT * FROM 表名 WHERE year=2014的SQL查詢語句,不同的是在選擇的列名和列的維數(shù)上可能存在限制,比如實際的SQL語句可能是SELECT column1,column2,column3 FROM 表名 WHERE year=2014,這樣就會導(dǎo)致在后期進行注入的時候需要考慮SQL語句中選擇語句的維數(shù)限制,使用union語句進行注入時,可以通過PHP頁面的數(shù)據(jù)維數(shù)輔助確定SELECT語句的維數(shù)。從頁面上可以看出SQL語句含有三列數(shù)據(jù)。使用union語句注入首先要查看MySQL中含有哪些數(shù)據(jù)庫,可以在瀏覽器中構(gòu)造URL請求:http://127.0.0.1:8081/search.php?year=2014%20and%201=2%20union%20select%20 SCHEMA_NAME,SCHEMA_NAME,SCHEMA_NAME%20from%20information_schema.SCHEMATA%20limit%200,6,可以獲取服務(wù)器上安裝的數(shù)據(jù)庫名,如圖3所示,隨后構(gòu)造請求字符串:http://127.0.0.1:8081/search.php?year=2014%20and%201=2%20union%20select%20TABLE_NAME,TABLE_NAME,TABLE_NAME%20from%20information_schema.TABLES%20where%20TABLE_SCHEMA=%27sample%27%20limit%200,3,可以獲取服務(wù)器上的數(shù)據(jù)庫表名,如圖4所示,隨后構(gòu)造請求字符串:http://127.0.0.1:8081/search.php?year=2014%20and%201=2%20Union%20select% 20COLUMN_NAME,COLUMN_NAME,COLUMN_NAME%20from%20information_schema.COLUMNS%20where%20TABLE_NAME=%27login%27%20limit%200,3,可以獲取數(shù)據(jù)庫表中所含有的字段名,如圖5所示,下一步可以構(gòu)造請求字符串:http://127.0.0.1:8081/search.php?year=2014%20UNION% 20SELECT%201,pass,3%20from%20login%20WHERE%20id=1,如圖6所示。
圖3 獲取數(shù)據(jù)庫名
圖4 獲取數(shù)據(jù)庫表名
圖5 獲取表字段名
通過以上幾個步驟就可以獲得用戶的密碼等相關(guān)信息。
(3) 使用INSERT語句
圖7 用戶登錄界面
圖8 使用INSERT語句進行注入
INSERT語句注入主要結(jié)合SQL語句中的注釋符使程序預(yù)定插入的值變成SQL注釋部分,從而達到注入的目的。使用insert語句注入主要是在注冊頁面進行注入,圖7是一個普通的用戶注冊頁面,用戶在輸入用戶名密碼后,系統(tǒng)會自動地給用戶賦予權(quán)限值3,在數(shù)據(jù)庫端的SQL語句是INSERT INTO reg(uname,pass,power) VALUES('user1','userpass',3),可以利用MYSQL的注釋字符對其進行注入,測試用例在pass字段上進行注入,如圖8所示,原理是通過人為構(gòu)造“’”,使得原本合法的SQL語句權(quán)限部分失效;如果要使得注入的權(quán)限值有效,只需在用戶密碼框字段輸入user1pass',1)#,用戶名字段輸入user1即可在系統(tǒng)創(chuàng)建一個用戶名為user1、密碼為user1pass、權(quán)限是1的用戶,如圖9所示。
(1)對輸入的數(shù)據(jù)進行過濾(過濾輸入)
1)對于動態(tài)構(gòu)造SQL查詢的場合,可以使用替換字符和刪除特殊字符的方法。替換字符是把所有單獨出現(xiàn)的單引號改成兩個單引號,以防止攻擊者修改SQL命令的含義。刪除特殊字符,是防止攻擊者構(gòu)造出類似“select * from Userswhere admin = 'AAA' —— and password =”之類的查詢,因為這類查詢的后半部分已經(jīng)被注釋掉,不再有效,攻擊者只要知道一個合法的用戶登錄名稱,根本不需要知道用戶的密碼就可以順利獲得訪問權(quán)限。
2)檢查用戶輸入的合法性,防止非法數(shù)據(jù)輸入。數(shù)據(jù)檢查應(yīng)當(dāng)在客戶端和服務(wù)器端執(zhí)行。執(zhí)行服務(wù)器端驗證,是為了彌補客戶端驗證機制的不足。因為在客戶端,攻擊者很有可能獲得網(wǎng)頁的源代碼,修改驗證合法性的腳本(或者直接刪除腳本),然后將非法內(nèi)容通過修改后的表單提交給服務(wù)器。因此,為了確認是否執(zhí)行了驗證操作,就需在服務(wù)器端也執(zhí)行驗證,可以使用許多內(nèi)建的驗證對象,例如RegularExpressionValidator,也可采用插入服務(wù)器端的方法調(diào)用,同時系統(tǒng)可以檢測系統(tǒng)用戶的輸入數(shù)據(jù)內(nèi)容,發(fā)現(xiàn)含有輸入語句時,可以將頁面轉(zhuǎn)發(fā)到系統(tǒng)出錯頁面以規(guī)避攻擊者不斷地向系統(tǒng)注入式攻擊導(dǎo)致系統(tǒng)處理性能下降。圖10是測試用的普通用戶注冊頁面,圖11是進行INSERT語句注入錯誤處理頁面,圖12是系統(tǒng)后臺對用戶輸入進行檢測的狀態(tài)監(jiān)控的頁面,可以向系統(tǒng)管理員顯示攻擊者輸入的注入語句,以方便管理員及時發(fā)現(xiàn)系統(tǒng)漏洞并加以修補。
圖10 注冊頁面
3)限制表單或查詢字符串輸入的長度范圍。如果用戶的登錄名字范圍在6~10個字符,那么不要認可表單中輸入的6個以下和10個以上的字符,從而增加攻擊者在SQL命令中插入惡意代碼的難度。
圖12 系統(tǒng)檢測頁面
4)加密用戶登錄名稱、密碼等數(shù)據(jù)。加密輸入的數(shù)據(jù)后,再將它與數(shù)據(jù)庫中保存的數(shù)據(jù)進行比較,這就相當(dāng)于對用戶輸入的數(shù)據(jù)進行了“消毒”處理,用戶輸入的數(shù)據(jù)不再對數(shù)據(jù)庫有任何特殊的意義,從而也就防止了攻擊者注入SQL命令。
5)檢查提取數(shù)據(jù)的查詢所返回的記錄數(shù)量。假如程序只要求返回一定數(shù)目的記錄,但實際返回的記錄卻超出了程序要求返回的數(shù)目,那就把該查詢操作當(dāng)作非法處理。
過濾輸入內(nèi)容可以按多種方式進行,示例如下。
*過濾sql與php文件操作的關(guān)鍵字
private function filter_keyword( $string ){
$keyword='select|insert|update|delete|’|‘〔|〔|。?!畖?!畖union|into|load_file|outfile';
$arr = explode('|', $keyword);
$result = str_ireplace($arr, '', $string);
return $result;
}
*檢查輸入的數(shù)字是否合法,合法返回對應(yīng)id,否則返回false
protected function check_id( $id ){
$result = false;
if($id!== '' && !is_null($id)){
$var= $this->filter_keyword( $id ); // 過濾sql與php文件操作的關(guān)鍵字
if ( $var !== '' && !is_null( $var ) &&is_numeric( $var ) ){
$result = intval( $var );
}
}
return $result;
}
* 檢查輸入的字符是否合法,合法返回對應(yīng)id,否則返回false
protected function check_str($string){
$result = false;
$var = $this->filter_keyword( $string ); // 過濾sql與php文件操作的關(guān)鍵字
if(!empty($var)){
if(!get_magic_quotes_gpc()){ // 判斷magic_quotes_gpc是否為打開
$var = addslashes($string); // 進行magic_quotes_gpc沒有打開的情況對提交數(shù)據(jù)的過濾
}
$var = str_replace("_", "—", $var); // 把 '_'過濾掉
$var = str_replace("%", "\%", $var); // 把 '%'過濾掉
$var = nl2br($var); // 回車轉(zhuǎn)換
$var = htmlspecialchars( $var ); // html標(biāo)記轉(zhuǎn)換
$result = $var;
}
return $result;
}
(2)對發(fā)送到數(shù)據(jù)庫的數(shù)據(jù)進行轉(zhuǎn)義(轉(zhuǎn)義輸出)
盡量使用為自定義數(shù)據(jù)庫設(shè)計的轉(zhuǎn)義函數(shù)。如果沒有,使用函數(shù)addslashes()是比較好的方法,對字符串型參數(shù)使用mysql_real_escape_string函數(shù)、對數(shù)字型參數(shù)使用intval,floatval函數(shù)強制過濾則更好。當(dāng)所有用于建立一個SQL語句的數(shù)據(jù)被正確過濾和轉(zhuǎn)義時,實際上也就避免了SQL注入的風(fēng)險。
如對mysql用戶,可以使用mysqlreal_escape_string函數(shù)實現(xiàn)。mysql_real_escape_string會調(diào)用MySQL的庫函數(shù)mysql_real_escape_string,對(x00), ( ), ( ), (), (‘), (x1a)進行轉(zhuǎn)義,即在前面添加反斜杠(),預(yù)防SQL注入。但該函數(shù)并不轉(zhuǎn)義 % 和 _。最好不要對整條sql語句使用該函數(shù),而是只轉(zhuǎn)義轉(zhuǎn)入sql語句的字符串參數(shù),否則會發(fā)生意想不到的結(jié)果。
addslashes對SQL語句中的特殊字符進行轉(zhuǎn)義操作,包括(‘), (“), (), (NUL)四個字符,此函數(shù)在DBMS沒有自己的轉(zhuǎn)義函數(shù)時候使用,但是如果DBMS有自己的轉(zhuǎn)義函數(shù),那么推薦使用原裝函數(shù),比如MySQL有mysql_real_escape_string函數(shù)用來轉(zhuǎn)義SQL。
htmlentities把HTML中可以轉(zhuǎn)義的內(nèi)容轉(zhuǎn)義成HTML Entity。html_entity_decode為htmlentities的decode函數(shù)。
htmlspecialchars把HTML中的幾個特殊字符轉(zhuǎn)義成HTML Entity(格式:&xxxx;)形式,包括(&),(‘),(“),(<),(>)五個字符。htmlspecialchars可以用來過濾$GET,$POST,$COOKIE數(shù)據(jù),預(yù)防XSS。注意htmlspecialchars函數(shù)只是把認為有安全隱患的HTML字符進行轉(zhuǎn)義,如果想要把HTML所有可以轉(zhuǎn)義的字符都進行轉(zhuǎn)義的話請使用htmlentities。
(3)打開magic_quotes_gpc或 magic_quotes_runtime 選項
打開magic_quotes_gpc選項,并關(guān)閉錯誤提示display_errors=off,不給攻擊者提供敏感信息,這樣所有的客戶端GET和POST的數(shù)據(jù)都會自動進行addslashes處理,使得對字符串值的SQL注入不可行。
打開magic_quotes_runtime 選項時,從文件中讀取的數(shù)據(jù)或執(zhí)行exec()的結(jié)果或是從SQL查詢中得到的,數(shù)據(jù)都會自動進行轉(zhuǎn)義。
(4)使用PDO連接數(shù)據(jù)庫
防止SQL注入最好的方法就是不要自己設(shè)置SQL命令和參數(shù),而是用PDO的prepare和bind,原理就在于要把SQL查詢命令和傳遞的參數(shù)分開。使用prepare的時候,DB server會把SQL語句解析成SQL命令。使用bind的時候,只是動態(tài)傳遞DB Server解析好的SQL命令。
另外使用rewrite技術(shù)隱藏真實腳本及參數(shù)的信息,通過rewrite能過濾可疑的參數(shù),而不必使用具有FILE權(quán)限的賬號(比如root)來連接MySQL,也能屏蔽load_file等危險函數(shù)。
針對PHP的網(wǎng)站存在多種攻擊方式,如命令注入(Command Injection)、eval注入(Eval Injection)、客戶端腳本攻擊(Script Insertion)、跨網(wǎng)站腳本攻擊(Cross Site Scripting, XSS)、SQL注入攻擊(SQL injection)、跨網(wǎng)站請求偽造攻擊(Cross Site Request Forgeries, CSRF)、Session 會話劫持(Session Hijacking)、Session 固定攻擊(Session Fixation)、HTTP響應(yīng)拆分攻擊(HTTP Response Splitting)、文件上傳漏洞(File Upload Attack)、目錄穿越漏洞(Directory Traversal)、遠程文件包含攻擊(Remote Inclusion)、動態(tài)函數(shù)注入攻擊(Dynamic Variable Evaluation)、URL攻擊(URL attack)、表單提交欺騙攻擊(Spoofed Form Submissions)、HTTP請求欺騙攻擊(Spoofed HTTP Requests)等,其中SQL注入攻擊較多。SQL注入是從正常的WWW端口訪問,而且表面上跟一般的Web頁面沒什么區(qū)別,所以目前市面的防火墻都不會對SQL注入發(fā)出警報,對其必須加強技術(shù)防范。SQL注入攻擊主要針對的是應(yīng)用開發(fā)過程中編程的不嚴密,因而防范SQL注入攻擊的主要方法就是完善編程,另外對服務(wù)器端進行合理配置,封住SQL注入漏洞,如把IIS設(shè)置成屏蔽錯誤提示,也能有效防范SQL注入攻擊。
參考文獻:
[1] 數(shù)位文化. php4交互網(wǎng)頁數(shù)據(jù)庫實戰(zhàn)手冊[M].北京:清華大學(xué)出版社,2004.
[2] 渠芳,曹志梅.ASP,PHP和JSP技術(shù)的比較研究[J].現(xiàn)代情報,2002 (7):50-52.
[3] 龍浩.PHP語言進階和高級應(yīng)用[M].北京:清華大學(xué)出版社,2002.
[4] 焦桐順. phpmysql數(shù)據(jù)庫開發(fā)指南[M].北京:電子工業(yè)出版社, 2001.
[5] Hugh E.Williams. PHP & MYSQL Web數(shù)據(jù)庫應(yīng)用開發(fā)指南[M].謝君英,歐陽宇,譯.南京:東南大學(xué)出版社,2003.
[6] 陳浩.PHP 程序設(shè)計[M].北京:電子工業(yè)出版社,2005.
[7] 邵煜.PHP和MYSQL WEB開發(fā)[M].北京:機械工業(yè)出版社,2005.
[8] David Lane.PHP & MYSQL WEB數(shù)據(jù)庫應(yīng)用開發(fā)指南[M].南京:東南大學(xué)出版社,2006.
[9] Mihai Bucica.AJAX與PHP WEB開發(fā)[M].北京:人民郵電出版社,2007.
[10] PETER MOULDING.PHP技術(shù)內(nèi)幕[M].北京:中國水利水電出版社,2003.