董沛然
(國(guó)家開(kāi)發(fā)銀行信息科技部,北京 100032)
軟件的注入類漏洞,是風(fēng)險(xiǎn)等級(jí)很高的一類漏洞,在CVE、OWASP等權(quán)威信息安全漏洞庫(kù)中,注入類漏洞長(zhǎng)期位居前10名之列。它可以導(dǎo)致系統(tǒng)運(yùn)行錯(cuò)誤、或泄露客戶信息等嚴(yán)重事故。
在所有注入類漏洞中,尤以SQL注入最為常見(jiàn),危害也最大。
SQL注入,簡(jiǎn)單地說(shuō)就是利用程序代碼漏洞,繞過(guò)程序權(quán)限,將SQL命令插入到用戶請(qǐng)求的查詢字符串或者輸入域進(jìn)行攻擊,其結(jié)果輕則獲得敏感信息和數(shù)據(jù),重則控制服務(wù)器[1]。
攻擊者(黑客)在利用SQL漏洞實(shí)施攻擊時(shí),通常會(huì)選擇直接注入或二次注入兩種方式。在直接SQL注入方式中,直接將代碼插入到用戶輸入變量,該變量與SQL命令串聯(lián)在一起;在二次SQL注入方式中,將惡意字符串通過(guò)報(bào)文植入數(shù)據(jù)庫(kù)表中,未來(lái)在程序讀取該庫(kù)表字段時(shí),被動(dòng)態(tài)拼接為SQL命令,并執(zhí)行。
下面以一個(gè)簡(jiǎn)單的直接注入SQL漏洞為例,說(shuō)明SQL注入的原理。以下代碼動(dòng)態(tài)地構(gòu)造并執(zhí)行了一個(gè)SQL查詢,該查詢可以搜索與指定客戶名稱相匹配的交易記錄。
假設(shè)有如下C語(yǔ)言代碼段:
Receive_Input(&szCUSTOM_NAME); //接收用戶輸入的查詢條件——注入點(diǎn)

上述代碼的邏輯是:接收用戶在界面輸入的姓名,將其作為查詢條件拼接到SQL語(yǔ)句中,再在數(shù)據(jù)庫(kù)中完成查詢操作。
正常情況下,當(dāng)用戶輸入客戶名稱David時(shí),拼接成的SQL語(yǔ)句為:
select * from TABLE_TRANSACTION where CUSTOM_NAME='David';
此時(shí)程序?qū)a(chǎn)生正確的輸出。
但是當(dāng)這段代碼被黑客攻擊時(shí),如果黑客輸入的內(nèi)容為:

這樣一來(lái),將會(huì)有兩條SQL語(yǔ)句被執(zhí)行,雖然其中前一條SQL語(yǔ)句是安全的,但后一條卻可以查詢出庫(kù)表中的所有記錄,造成嚴(yán)重的信息泄露。
這里涉及一些概念。在這個(gè)例子中,程序從用戶輸入獲取了szCUSTOM_NAME字段,在未經(jīng)過(guò)濾的情況下,將其值直接以字符串拼接的方式拼接到一個(gè)SQL語(yǔ)句里面,造成了SQL注入漏洞。
在這段程序中,直接從用戶輸入獲取信息的代碼行Receive_Input(&szCUSTOM_NAME)稱為這個(gè)SQL注入漏洞的注入點(diǎn)。執(zhí)行危險(xiǎn)SQL語(yǔ)句的代碼行Execute(szSQL)稱為這個(gè)SQL注入漏洞的爆發(fā)點(diǎn)。從注入點(diǎn)到爆發(fā)點(diǎn)之間,所有涉及危險(xiǎn)字符串szCUSTOM_NAME信息賦值的語(yǔ)句,稱為這個(gè)SQL注入漏洞的傳遞鏈。
命令注入漏洞,是指通過(guò)提交惡意構(gòu)造的參數(shù)破壞命令語(yǔ)句的結(jié)構(gòu),達(dá)到非法執(zhí)行命令的手段。命令注入漏洞常發(fā)生在具有執(zhí)行系統(tǒng)命令的Web應(yīng)用中。
下面舉一個(gè)命令注入漏洞的例子。以下代碼在服務(wù)器上動(dòng)態(tài)地為用戶建立專用目錄,路徑名是用戶輸入的客戶名稱。
假設(shè)有如下PHP語(yǔ)言代碼段:

如此看來(lái)上述代碼也是存在很大風(fēng)險(xiǎn)的。
在本例中,同樣涉及注入點(diǎn)、爆發(fā)點(diǎn)、數(shù)據(jù)流的概念。從用戶輸入獲取cu st om Na me的代碼行$customName = $_POST["customName"]稱為這個(gè)命令注入漏洞的注入點(diǎn)。執(zhí)行這個(gè)危險(xiǎn)命令的代碼行system($command)稱為這個(gè)命令注入漏洞的爆發(fā)點(diǎn)。從注入點(diǎn)到爆發(fā)點(diǎn)之間,所有涉及危險(xiǎn)字符串customName信息賦值的語(yǔ)句,稱為這個(gè)命令注入漏洞的傳遞鏈。
從上面兩個(gè)例子可以看出,注入類漏洞的本質(zhì)是,黑客通過(guò)引號(hào)、分號(hào)、斜杠、點(diǎn)號(hào)等特殊字符,閉合了原有的程序邏輯,使得用戶提交的參數(shù)和程序本來(lái)的邏輯相互干擾。這樣一來(lái),黑客提交的參數(shù)就不僅僅只起到參數(shù)的作用,而且還進(jìn)入到程序邏輯里來(lái)了。黑客就是通過(guò)這樣的方式,把惡意代碼嵌入到正常代碼里來(lái)的。
根據(jù)注入類漏洞的可利用性和危害性,目前業(yè)界一般將其分為三類。
(1)高危漏洞:可以直接被利用的漏洞,并且利用難度較低。利用之后可能對(duì)網(wǎng)站或服務(wù)器的正常運(yùn)行造成嚴(yán)重影響、對(duì)用戶財(cái)產(chǎn)及個(gè)人信息造成重大損失。
(2)中危漏洞:利用難度極高,或滿足嚴(yán)格條件才能實(shí)現(xiàn)攻擊的漏洞,或漏洞本身無(wú)法被直接攻擊,但能為進(jìn)一步攻擊起較大幫助作用的漏洞。
(3)低危漏洞:無(wú)法直接實(shí)現(xiàn)攻擊,但可能造成一些非重要信息的泄露,這些信息可能讓攻擊者更容易找到其他安全漏洞。
從上節(jié)可見(jiàn),注入漏洞的本質(zhì)是利用程序代碼缺陷,繞過(guò)程序的權(quán)限,將惡意代碼插入到用戶請(qǐng)求的查詢字符串或者輸入域進(jìn)行攻擊。
為了防范注入類漏洞,業(yè)界主流的做法是加強(qiáng)對(duì)用戶輸入內(nèi)容的校驗(yàn)。通常在各種大型信息系統(tǒng)的界面中,諸如時(shí)間、日期等大多數(shù)輸入框的格式是固定的,黑客無(wú)法通過(guò)這些輸入框注入攻擊信息。但是對(duì)于姓名、備注欄等較為開(kāi)放的輸入框,系統(tǒng)一般不對(duì)輸入內(nèi)容做過(guò)多校驗(yàn),并且此類輸入框的長(zhǎng)度一般較長(zhǎng),足夠黑客輸入可運(yùn)行的惡意內(nèi)容。
有鑒于此,出于防范注入類型攻擊的目的,筆者認(rèn)為進(jìn)行大型信息系統(tǒng)的開(kāi)發(fā)時(shí)應(yīng)做到以下5點(diǎn)。
在編碼層面,編寫安全性高的源代碼,避免SQL拼接,改用占位符方式實(shí)現(xiàn)SQL組裝。重視源代碼安全檢查,因?yàn)樵创a安全檢查可以暴露一部分明顯的安全隱患(如SQL拼接等)[2]。
在前面的案例中,造成SQL注入攻擊的根本原因在于攻擊者可以改變SQL查詢的上下文,使本應(yīng)作為數(shù)據(jù)解析的數(shù)值,被篡改為命令了。為避免這種問(wèn)題,以占位符形式給SQL語(yǔ)句傳參是一種十分有效的方法。
使用占位符生成SQL語(yǔ)句的示例代碼段如下:

在上面的示例代碼段中,問(wèn)號(hào)表示占位符。在程序編譯時(shí)將參數(shù)傳入SQL語(yǔ)句,生成合法的SQL語(yǔ)句。如此一來(lái),非程序自身的數(shù)據(jù)不參與SQL語(yǔ)句邏輯的構(gòu)成,那么黑客精心設(shè)計(jì)的危險(xiǎn)字符串也就不會(huì)奏效了。
對(duì)所有開(kāi)放型輸入框,進(jìn)行惡意關(guān)鍵字的校驗(yàn),如前面案例中的delete、rm、分號(hào)、斜杠等字符串應(yīng)盡量過(guò)濾掉。危險(xiǎn)字符串可用窮舉或正則表達(dá)式的方式識(shí)別[3]。過(guò)濾時(shí),可以考慮去掉這些可能隱藏惡意攻擊意圖的字符串,或用星號(hào)替換。
堅(jiān)持最小展現(xiàn)原則,客戶端的展示信息不宜過(guò)細(xì)。這是因?yàn)楹诳驮诶米⑷胧侄喂粝到y(tǒng)時(shí),往往免不了“猜”和“試”的過(guò)程。黑客通常通過(guò)反復(fù)試探,并借助系統(tǒng)的各種返回信息、消息、日志內(nèi)容來(lái)猜測(cè)其開(kāi)發(fā)語(yǔ)言、后臺(tái)架構(gòu)、甚至庫(kù)表字段名稱等細(xì)節(jié)信息[4]。因此,我們?cè)谲浖_(kāi)發(fā)中,要盡量避免給用戶暴露系統(tǒng)細(xì)節(jié)(如應(yīng)用程序信息、數(shù)據(jù)庫(kù)信息、或其他容易暴露后臺(tái)邏輯的信息)[5]。不但要避免在客戶端上顯式地展示這些信息,也不宜寫在消息或日志里返回給客戶端,因?yàn)楹诳涂梢岳昧髁孔グ浖ト〉竭@些消息或日志信息。
系統(tǒng)的每一個(gè)進(jìn)程、每一次數(shù)據(jù)庫(kù)操作,應(yīng)該使用可以完成該任務(wù)的最小權(quán)限運(yùn)行。任何需要提權(quán)的操作,都應(yīng)盡可能只保持最短的時(shí)間,一旦任務(wù)完成,應(yīng)該立即收回權(quán)限,這樣可以減少攻擊者在高權(quán)限的條件下執(zhí)行惡意代碼的機(jī)會(huì)。
系統(tǒng)在進(jìn)行惡意關(guān)鍵字的過(guò)濾時(shí),不僅需要考慮本系統(tǒng)所用的開(kāi)發(fā)語(yǔ)言,還應(yīng)考慮有關(guān)聯(lián)關(guān)系的其他下游系統(tǒng)。一個(gè)大型復(fù)雜系統(tǒng),往往是由眾多小型子系統(tǒng)構(gòu)成的。如圖1所示。

圖1 大型復(fù)雜系統(tǒng)模型(無(wú)輸入校驗(yàn))
其中,接入渠道子系統(tǒng)A主要負(fù)責(zé)接收用戶的輸入信息,并做一些簡(jiǎn)單的處理。而后,根據(jù)一定的邏輯,A將數(shù)據(jù)流輸入到后臺(tái)處理子系統(tǒng)B和后臺(tái)處理子系統(tǒng)C,進(jìn)行一些更為復(fù)雜的處理。
如前所述,由于系統(tǒng)遭受的注入類攻擊主要來(lái)自于用戶輸入,所以接入渠道系統(tǒng)一般擁有比較完善的校驗(yàn)規(guī)則,如下圖所示,接入渠道子系統(tǒng)A的輸入校驗(yàn)?zāi)K應(yīng)該可以過(guò)濾針對(duì)本子系統(tǒng)A的惡意字符。而后臺(tái)處理子系統(tǒng)B和C由于并不直接面向用戶,所以在開(kāi)發(fā)時(shí),往往這類子系統(tǒng)的安全性校驗(yàn)就不那么完整和嚴(yán)謹(jǐn)了。如圖2所示。

圖2 大型復(fù)雜系統(tǒng)模型(在接入渠道子系統(tǒng)A中加入輸入校驗(yàn))
但是,經(jīng)過(guò)接入渠道子系統(tǒng)A處理過(guò)的數(shù)據(jù)會(huì)流入后臺(tái)處理子系統(tǒng)B和C中,并且B、C所用的開(kāi)發(fā)語(yǔ)言、數(shù)據(jù)庫(kù)、部署的操作系統(tǒng)可能與接入渠道系統(tǒng)完全不同,黑客的惡意內(nèi)容完全有可能躲過(guò)了前面的校驗(yàn),而待轉(zhuǎn)入后面的系統(tǒng)后發(fā)起攻擊。這就如同地鐵線路中,一顆炸彈一旦從某站流入地鐵,就可以暢通無(wú)阻地流竄到任意線路、任意站點(diǎn)。所以要想保證所有線路的安全,就要確保全市任一地鐵站,與重要站點(diǎn)的安檢同樣嚴(yán)格。因此更好的做法是,在每個(gè)子系統(tǒng)中,都針對(duì)本子系統(tǒng)的技術(shù)特點(diǎn),進(jìn)行有針對(duì)性的校驗(yàn)。如圖3所示。

圖3 大型復(fù)雜系統(tǒng)模型(在每個(gè)子系統(tǒng)中都加入輸入校驗(yàn))
當(dāng)然,這種各個(gè)子系統(tǒng)分別校驗(yàn)的方式容易形成“各自為政”,可能接入渠道子系統(tǒng)A中已經(jīng)實(shí)現(xiàn)了的輸入校驗(yàn),在后臺(tái)處理子系統(tǒng)B和C中又重復(fù)實(shí)現(xiàn)了,這必然會(huì)降低系統(tǒng)的開(kāi)發(fā)效率和運(yùn)行效率。
有鑒于此,最好的方式是在接入渠道子系統(tǒng)A的前面,建立專門的校驗(yàn)系統(tǒng),涵蓋針對(duì)所有子系統(tǒng)的潛在危險(xiǎn)字符校驗(yàn),供整個(gè)系統(tǒng)群使用。如圖4所示。

圖4 大型復(fù)雜系統(tǒng)模型(在接入渠道子系統(tǒng)A前加入專門的輸入校驗(yàn)?zāi)K)
這樣不僅達(dá)到了全面校驗(yàn)的效果,并且也避免了重復(fù)校驗(yàn)的危害,充分保證系統(tǒng)群的整體運(yùn)行效率。不僅如此,從開(kāi)發(fā)人員安排上,可以請(qǐng)專門的信息安全人員編寫此安全模塊(或子系統(tǒng)),在保證專業(yè)性的同時(shí),也有利于其他子系統(tǒng)的開(kāi)發(fā)人員將精力集中于業(yè)務(wù)處理上。
本文首先介紹了注入類安全缺陷的定義和原理,然后,從安全架構(gòu)、安全編碼的角度,提出了5點(diǎn)防范注入類缺陷的措施:以占位符形式給SQL語(yǔ)句傳參、嚴(yán)格進(jìn)行惡意關(guān)鍵字的校驗(yàn)、客戶端的展示信息不宜過(guò)細(xì)、堅(jiān)持最小權(quán)限原則、遵循系統(tǒng)的整體安全架構(gòu)模型。最后,從安全測(cè)試的角度,提出了基于復(fù)合引擎的自動(dòng)化代碼安全檢查平臺(tái)的構(gòu)建方法,并闡述了其優(yōu)勢(shì)、原理和具體算法。■