有一類的安全性問題,根源並不在機制設計之上,而是出自於程式設計者本身所撰寫程式碼的毛病。而這類問題,跟程式設計者更是息息相關,而且無法透過對架構的審核來察覺。因為每個程式設計者都有可能犯下這種類型的錯誤,而這卻有可能造成嚴重的安全性問題。

因為程式設計瑕疵而導致的安全性問題中,最為惡名昭彰、歷史也最為久遠的問題,便是所謂的緩衝區溢位問題(buffer overflow)。

造成緩衝區溢位狀況的基本原理
在前文中曾經提到第一個透過網路來進行感染、擴散的Morris Worm,便是利用所謂緩衝區溢位的技巧來進行攻擊的一個有名的例子。在那之後,有數不盡的惡意程式,都利用了這個技巧來對軟體進行攻擊,藉以入侵到被攻擊的程式,執行自己想要執行的程式碼片段。

在Unix上著名的電子郵件系統sendmail,曾經有一段時期是有心人士們喜歡攻擊的對象。而攻擊的方式中便包括了緩衝區溢位的手法。但每當sendmail修補了某些緩衝區溢位的問題後釋出了新版本,新版本時常又會再遭受到不同程式碼片段中的緩衝區溢位攻擊,可見,這個問題在當時的系統安全性上造成了多大的威脅。

而何謂緩衝區溢位攻擊?最簡單的一種,便是針對堆疊上的緩衝區進行攻擊。

C/C++這兩種常見的程式語言,會將區域變數所佔用的記憶體空間配置在堆疊中。在這兩種語言中,在處理字串時,時常可以看到程式設計者把字串需要的空間宣告成區域變數,例如:

char buf[128];

接著,便會在這空間中處理字串。有時候很有可能是接受外部的輸入,例如來自於使用者所輸入的字串,或者是經由網路所接受到客戶端的輸入資料。由於要在區域變數中宣告字串所需的記憶體空間,所以空間會配置在堆疊中。也正因為是要配置在堆疊裡,所以必須事先指定空間大小,以利編譯。

程式中可能會利用一些字串處理的C標準函式來對buf變數進行操作,像是:

strcpy(buf, input);

這是我們很常見到的寫法,卻也暗藏兇險。

問題出在,程式設計者如何確定input所指向的字串長度,會在buf所配置128位元組的限制之內呢?這是一個盲點。或許,在正常的使用情況下,input總是在128位元組之內,但是,別有居心的人,可能就不見得會這樣乖乖提供輸入資料了。

那麼,輸入的資料長度超過緩衝區的128位元組時,會發生什麼事?

因為一般CPU的設計,在呼叫副程式時,會將副程式的返回位址先推入堆疊之中,待副程式執行完畢後,便會自堆疊中取出返回位址,接著回到主程式中的呼叫點後的下一個指令。當輸入的資料長度超過原先緩衝區所配置的長度時,便有可能覆寫到主程式呼叫副程式時所推入堆疊的返回位址。

這麼一來,當對緩衝區的寫入發生了溢位(也就是超過了配置的空間大小時),便有機會破壞到副程式的返回位址,使得副程式返回時,跳到記憶體空間中不知名的地方,甚至不是擺放程式指令的區域,程式自然也就執行異常了。

如果這只是個單純的臭蟲也就算了,因為這種情況,造成了外部輸入的資料,足以控制程式的執行流程──因為透過緩衝區溢位,可以改變接下來程式的執行點,所以有心人士便可以加以利用。

紅色警戒之後
除了改變程式流程之外,利用緩衝區溢位的攻擊,時常也會在輸入資料中塞入機械程式碼,造成溢位之後,將程式流程改變至自己所置入的程式碼位址,接著,等於是隨心所欲的執行自己想要的任何動作了。

許多作業系統,都會將root或是administrator的權限和一般使用者區分開來,許多關係到系統運作的重要動作,都必須獲得root或administrator權限才能執行。倘若被植入程式碼執行的程式,正好處於得到root權限的時候,那麼,基本上,這段被植入的程式碼便成了為所欲為的惡獸了。

在2001年,Windows的用戶曾遭受到一個CodeRed惡意程式的威脅,造成了一股很大的轟動,也造成很大的電腦及網路災難。有別於傳統Windows上的電腦病毒感染手法,CodeRed並不是利用執行於宿主之上的感染程式再感染其他程式的方式,而是直接利用網路餵入資料。在這之後,所謂的病毒感染,又有了截然不同的一片天了。

是怎麼攻擊的?它主要是利用Windows上主要的Web伺服器IIS的緩衝區溢位問題下手的。

所有的Web伺服器,都必須接受來自於外界的HTTP請求資料,在HTTP請求資料中,最重要的,當然是請求的網址。當時的IIS便是利用一個緩衝區來暫存、處理HTTP請求資料中的網址。一般來說,正常的瀏覽器或HTTP客戶端程式,不會發送過長的網址,也就不會在IIS上造成溢位的情況。但有心人士總是不會照著正常方式來和你的程式互動,CodeRed的作者找到了IIS的這個毛病,然後透過HTTP請求資料中的網址字串,進行了緩衝區溢位的攻擊。以這為入口侵入了IIS,然後接著就自由自在地進行它想要做的惡意行為了。

這就是可怕的地方,因為使用長度有限的緩衝區來處理字串,十分普遍。但只要一不小心,沒有檢查到輸入字串的長度,就有可能發生溢位的情況。

如何避免緩衝區溢位攻擊
上述介紹的堆疊緩衝區溢位,只是緩衝區溢位攻擊中最簡單的形式,之外,還有一些不同類型、更進階的緩衝區溢位攻擊型態,但其實基本精神都類似。像是Heap上的緩衝區溢位、對於陣列索引值的溢位、甚至像C標準函式庫中的各種printf()中的格式化字串,都有可能引發。

要怎麼避免緩衝區溢位攻擊呢?當然,許多安全性攻擊都是從輸入資料著手。永遠抱著不相信輸入資料的態度是一個重要的基本原則。在防範緩衝區溢位攻擊時,最重要的便是不要相信輸入資料的長度。在緩衝區內處理輸入資料時,永遠要考慮到可能會有的過長資料,並且做出因應。

當然,即使程式設計者很努力防範,但人總是會犯錯。而安全性的問題,就算擋住了千分之九百九十九,只要有一點沒防到,就全盤皆輸。想要憑藉人的力量來防守,的確是辛苦了點。

一些較新的程式語言,例如像Java,便在語言本身的特性上進行了加強。在Java中,程式設計者沒有機會逾越緩衝區的邊界,自然難以引發溢位攻擊。有了程式語言先天的機制做倚靠,便能盡量減少人為的疏失。

對於仍舊使用C/C++的程式設計者來說,新型的編譯器也提供一些協助。而一些原始碼的掃描工具,也能幫你檢查出可能出問題的所在。新版的函式庫,甚至針對字串處理提供了更新、更安全、能指定長度限制的函式(舉例來說,舊式的strcpy()在做字串拷貝時,是不會考慮到緩衝區長度問題的,但新式的則會)。棄用舊式的字串處理函式,改用新式的字串處理函式,自然也有助於防範。

緩衝區溢位攻擊既可怕、又難以防守。想要安全程式碼,了解這個敵人絕對是首要功課之一。

專欄作者

熱門新聞

Advertisement