別讓危險成為預設的行為,讓危險的行為比安全的行為更麻煩

危險的行為

對於寫程式而言,很多預設的行為都是相當危險的,舉一些最常見的例子SQL Injection、XSS、Buffer overflow,我們可以從這些幾個最常出現被攻擊的類形,都有一個共同的特點,就是它們通常都是因為預設的行為很危險,我們一個一個來看

SQL Injection

這是最常見的網頁程式攻擊手法之一,例如有一個SQL語法這樣寫

SELECT * FROM Member WHERE id = '$userId'

userId沒有經過任何過濾,那麼這樣的程式等於打開了SQL的大門任惡意的使用者操作,考濾一下如果使用者帳號輸入這樣

‘; SHUTDOWN —

如此一來整句SQL語法就會變成這樣

SELECT * FROM Member WHERE id = ''; SHUTDOWN --'

SELECT的SQL語法後面被加上了一句Shutdown指令,如此一來SQL Server就會被關掉,當然這還稱不上太邪惡,要做任何事情幾乎都可以,只要SQL語法能辦到,很明顯地,危險來自預設的方式去下SQL指令,使用者,也就是程式設計師,如果沒有這方面的知識,那麼使用預設的行為是理所當然的事情,像PHP較後面的版本就有自動取代一些危險的字元,雖然有點麻煩,但至少它是一個方法,讓預設的行為不那麼的危險

XSS

接著是XSS,也就是Cross-site Scripting,這也一樣是危險的預設行為所引起的問題,假設我們有下面的一個簡單PHP程式

echo "Hello ";
echo $userName;

看起來只是一個無傷大雅跟使用者打招呼的程式,考慮一下$userName變數如果沒有經過任何過濾,如果使用者將userName輸入成下面的資料

這一樣是一個無傷大雅的小惡作劇,輸入這個資料會讓頁面標題被改成你想要的字串,但是把這程式碼改得更邪惡一點就不再是什麼有趣的事,很明顯地,這一樣又是一齣危險的預設行為所造成的悲劇,任何資料輸出都預設直接是完整地顯示出來,沒有經過任何過濾,像name這種顯而易見的欄位可能會被注意到並且記得過濾,那其它欄位呢? 今天我如果是攻擊者,任何可以輸入的欄位都是值得一試的,不是只有text box類形的表單元件才能輸入惡意的資料,因此過濾危險字元,便成了寫PHP網頁程式的重要任務,它是那麼的危險,但是它是預設行為

Buffer overflow

接著我們看Buffer overflow,這就算是比較困難一點的手法,SQL Injection那種只要把那句shutdown指令到處貼,連小孩子都有可能癱渙政府的網站,我相信只要寫一個bot去爬網站,然後輸入那句語法,被爬到的網站還是會有很多會掛掉,政府機構的網站也是外包的,裡面只要有一個菜鳥忘記濾語法(或是老練的工程師忘記過濾),整個網站就完蛋了,但是Buffer overflow的手法就不一樣了,考慮一下下面的程式碼

void foo(char *userName) {
    char temp[16];
    strcpy(temp, user);
    // do something here....
}

看起來不怎麼樣的函數,但卻潛藏著大大的危險,如果使用者名稱輸入的資料長度超過16字元,在stack中後面的資料就會被蓋掉,惡意經過設計的資料,會將後面的函數返回的位置蓋掉成想要執行的位置,例如資料本身夾帶一段惡意程式,其中函數返迴的位址改成資料夾帶的惡意程式的位址,如此一來就可以利用資料的輸入來使目的程式做想幹的壞事,原因一樣出在,strcpy預設的行為是不檢查字串長度的,但是因為要了解定址、Stack如何運作等底層的一些知識,絕對不是script kids可以輕易辦到的事,當然利用現成的工具就另當別論,但那還是script kids在幹的事

危險當預設行為之於安全當預設行為

PHP的預設行為通常都很危險,這就是我不太喜歡它的原因之一,需要有經驗的程式設計,並且用盡你的力氣想辦法去防堵任何可能的漏洞,你只要忘記一個地方,就等於打開了大門,之前做的辛苦就全白費了,不止是你,還有你得燒香拜拜祈求你的整個團隊裡沒有出任何一個天兵在程式的某個角落裡忘記過濾惡意資料,相較之下我使用Python寫的TurboGears開發了不少網站之後,發現它確確實實落實了”別讓危險成為預設的行為“的概念,所有在PHP見到那堆煩死人的危險預設行為都看不到

首先是SQL Injection,在TurboGears裡用的是SQLObject或是SQLAlchemy/Elixir等ORM的一層,使用者不必寫任何一句SQL語法,因為語法都由這層ORM產生,自然避開了SQL Injection的問題,任何資料透過這層都會被過濾掉,然而SQLAlchemy等ORM的完備性,並不會因此而限制到太多程式的自由

接著是XSS,TurboGears預設使用的是Kid Template,它是一款樣版引擎,任何輸出的HTML都必須是要合法的才能輸出,而且所有資料預設都是被過濾的,要輸出HTML的情況比較少,須要”明確地說出來”

例如下面一個簡單樣板

${userName}

我們在這裡看到的userName預設就是把html過濾掉了,那你可能會說,如果我想輸出html怎麼辦,其實很簡單,像這樣

${XML(userProfile)}

如你所見,Kid template用一個XML函數來明確地指出要輸出html,雖然那些資料也一樣須要自己過濾,但是想想看,80%以上的資料都只是資料只有20%以下的資料是html需要這樣輸出,而樣版引擎也處理了語法迴圈輸出等等的問題,你寶貴的開發時間不該浪費在過濾那些該死的惡意語法上不是嗎?

至於Buffer overflow,Python因為是高階的語言,天生就沒這種問題,除非c語言寫的模組有這樣的問題,又剛好有使用者輸入的資料會跑到那個c語言模組的漏洞

危險的行為應該要被明確地說出來

解決的方法,應該說在設計上,就應該要這麼做,讓危險的行為應該要被明確地說出來,例如echo預設就過濾所有包含在裡面的變數,想輸出html需要明確地指出,像是這樣

echo "User's profile : ";
echo XML($userProfile);

這麼一來,只有少數需要將變數內裝html輸出的資料要用XML函數明確地指出來輸出,除此之外,十之八九的資料都不需要過濾,那麼危險的行為當預設行為,明明那麼少用到,變成過濾的工作一直都浪費在這些少數的情況上,這就是PHP很大的敗筆之一

接著是SQL Injection,一樣讓危險的事情需要明確的講出來,又或著用一層ORM之類的避免直接的SQL語法產生和字串合成

接著是Buffer overflow,最大的兇手就是strcpy這個函數,最初就不應該讓這樣的函數設計,造成無數的漏洞,不過當初又有誰會想到有這樣的漏洞,strcpy因為沒檢查邊界所以很危險,預設應該讓它檢查邊界,又或著是,乾脆不要有這樣的函數存在,當然它的存在有一定的方便性,而且檢查邊界又考濾到效能的問題,所以最好的辦法,還是一樣讓危險的行為被明確地指出來,像這樣假設在C++之下,預設是檢查邊界的行為

strcpy(temp, username);

然而要不檢查邊界的寫法可能像這樣

strcpy(temp, username, NOCHECK);

以某種程度來說,讓危險的行為麻煩一點總是比較好的,人們都會選擇比較輕鬆的寫法,而比較麻煩的寫法,除非有特別的需要,才會特別明確地指出,甚至不希望使用者去用的話,還可以故意讓危險的行為越麻煩越好

最後

切記別讓危險的行為成為預設值,特別是那些佔少數特例、又是極度危險的行為,實在沒有理由讓它們程為預設或是最常用的方法,反而要特別來過濾

別讓危險成為預設的行為,讓危險的行為比安全的行為更麻煩

——

修改 : 原本strcpy誤植為strlen 奇怪= = 我怎麼會寫成strlen..

This entry was posted in 中文文章, 資訊安全 and tagged , , , , . Bookmark the permalink.

6 Responses to 別讓危險成為預設的行為,讓危險的行為比安全的行為更麻煩

  1. FreeXD says:

    這篇文章是大大寫的嗎?

  2. victor says:

    是的 我寫的

  3. WiyD-Luck says:

    請問大大寫程式寫多久了
    小弟今年大三要升大四了
    在無意間近來大大的blog
    收穫良多(請受小的一拜
    會寫程式.跟會寫一個能用的程式.差別好大…
    想問大大那些程式會造成的漏洞.是經過自己研讀還是有人指點才有如今的實力阿
    PS.小弟就讀的大學老只有教我怎麼寫程式.並沒有教我寫出一個可以用的程式..

  4. Linmic says:

    給樓上,或許那是你自己要去想辦法會的?

  5. Pingback: 網站製作學習誌 » [Web] 連結分享