編 寫 安 全 的 軟 件
一、概述 所謂安全的軟體是指程式碼在設計與實現的時候能夠經受得住惡意的攻擊。為什麼安全的軟體如此重要?因為隨著互連網的發展,大部分的電腦都串連到互連網上,這樣就很容易受到別人從遠程發起的攻擊。很多駭客從道德上來說,並不認為自己這樣的攻擊行為有什麼過錯,他們可能反而會認為你的系統不安全是你自己的問題。因此,作為程式的開發人員就應該時時刻刻考慮到被攻擊的威脅,在保護使用者的資料以及隱私方面做出更多努力。怎樣編寫安全的軟體呢?首先在軟體架構設計時,就要考慮安全性問題;其次在代碼實現中也應注意安全問題;最後測試也是一個非常重要的階段。安全性測試並非功能測試,它是以駭客的角度來測試軟體,模仿駭客的攻擊手段,這樣可以發現軟體中存在的很多安全性問題,有利於在開發的整個流程中加入安全性措施,使得整個流程具有防範威脅的機能。此外,加強程式員對於安全軟體開發技術的訓練,提高安全意識也非常重要。安全技術現在是一個很熱門的話題,安全技術方面的人才也非常缺乏,掌握安全方面的技術對於尋找合適工作也是很有好處的。
二、安全開發的過程 攻擊者的優勢與防禦者的困境主要表現在: 1、防禦者必須防守所有的點,而攻擊者則可以選擇防守最薄弱的地方作為切入點。 2、防禦者必須時刻警惕,不可懈怠;而攻擊者可以隨意進行攻擊。 3、防禦者僅能防守自己已知的東西,而攻擊者可以研究系統最薄弱的部分。 因此,軟體開發人員想要開發安全的軟體就需要做好以下幾件事情: 1、設計時考慮安全 設計時要有一個流程,這一流程要孕育著安全的系統,即整個流程都要保證系統的安全。要建立一個威脅模型,知道所受的威脅來自何方,並讓程式以最低的許可權來運行。 2、預設狀態的設定安全 除常用的準系統外,其餘功能預設時應該盡量關閉,以減少可能被攻擊的表面。另外也可以使用Visual Studio .NET中一些安全功能,比如編譯時間的/GS開關。 3、部署安裝要安全 要遵循部署時所規定的安全措施,建立有關安全部署的指導文檔,以及要使用一些工具來評估系統的安全性。 安全的軟體開發流程圖1所示。
三、安全設計的原則 1、減少受攻擊的表面 可採取的措施有深層防守,即要有多層的安全防守;使用者最小的使用許可權;預設設定必須安全。 2、從以往的錯誤中學習 如果以往的版本中有錯誤並因此而受到攻擊的話,就應該針對此種錯誤容易引起的某種攻擊,那麼在下一版本中就要進行相應的改進。 3、安全本身也是一種功能 在做開發計劃時,還應該考慮安全方面所需花費的時間問題。 在此重點對減少受攻擊的表面這一原則作簡要介紹: 程式中越少的代碼在預設的時候運行,那麼就會大大的減少被攻擊的機會。其好處還在於對系統管理員的技巧要求不高,使用者只要裝上就能很好的運行了,不需要很多的組態管理知識。另外,由於可被攻擊的範圍縮小,這樣也就大大緩減了修複安全性漏洞的緊迫性。 下面是一張低攻擊表面與高攻擊表面所採用技術的對照表:
Lower Attack Surface |
Higher Attack Surface |
TCP |
UDP |
Local-only |
Remote |
Authenticated |
Unauthenticated |
Managed Mobile Code |
Native Mobile Code |
Managed Code |
ActiveX |
Low privilege |
SYSTEM |
Turned off by default |
Running by default |
Essential features enabled |
All features enabled |
四、威脅的建模 通常有一個誤區,那就是認為只要在軟體中增加一些安全的功能,這一軟體就是安全的,其實不然。增加軟體安全的功能並不意味著所寫的軟體就是安全的,如程式中增加了加密功能,但是此段加密功能的代碼又剛好存在著緩衝區溢位問題,那麼攻擊者照樣可以進行攻擊。安全功能不等於軟體的安全,安全功能只是為軟體安全服務的一種功能,安全功能的代碼寫得不好也會影響軟體的安全性。為此,有必要對軟體中可能存在的威脅進行建模。因為如果不明白威脅在哪,那麼就很難編寫出一個很安全的軟體。知道了威脅在哪,就可以在軟體的設計、開發以及測試等各階段有意識的去防範這些隱患。 威脅建模的過程如下: 1、建一個資料流圖或UML圖,並把需要保護的資訊列成一個清單。 2、把可能存在的威脅進行分類,而STRIDE通常被用於對威脅的效果進行分類。STRIDE分別是由Spoofing, Tampering, Repudiation, Info Disclosure, Denial of Service, Elevation of Privilege的首字母組成。Spoofing是指攻擊者偽裝成別的使用者或是別的被本系統認可的系統,如張三裝成李四訪問系統,偷換了其自身的標識符。Tampering是指攻擊者篡改了系統中的資料以達到其不軌的企圖。Repudiation是指攻擊者闖入系統進行了某些破壞,但是系統管理員卻無法證實此件事是否發生。Info Disclosure是指資訊被泄露給本無許可權知道這些資訊的人。Denial of Service指攻擊者使系統的合法使用者無法正常使用系統功能,使得系統不能及時的對合法使用者的服務要求進行響應,不能服務真正應該得到服務的人。Elevation of Privilege指攻擊者通過一些非法的手段,使得自身使用系統的許可權被提升。 3、依據可能受到攻擊的機率以及由此所造成的破壞程度來對威脅進行分級。通常對威脅進行分級的依據稱為DREAD,DREAD分別是由Damage Potential、Reproducibility、Exploitability、Affected Users、Discoverability的首字母組成。Damage potential是指系統是否容易受到破壞。Reproducibility是指攻擊的可重複性,即第一次這樣攻擊,第二次是否還可以同樣的方式進行攻擊。Exploitability指可利用性,即該威脅可被利用的程度。Affected Users指該威脅可影響多大範圍的使用者。Discoverability是指可發現性,即漏洞是否會被發現,通常只要有漏洞就一定會被發現。 有了以上所述的威脅模型作參考,寫程式時就可以知道程式中最“危險”的部分是哪裡,這樣也就可以在做security push時進行重點的檢查,而且還有助於在編碼時就確定防禦的機制。此外,在做軟體測試時也就可以從最易受到攻擊的地方入手進行測試了。
五、輸入信任的問題 所有的安全性漏洞可被分成兩大類,Input trust issues(太信任使用者的輸入)和Everything else。下面將簡要介紹Input trust issues方面的內容。 請記住一句話,“All input is evil, until proven otherwise!”,意思是說所有的輸入都是非常壞的,直到你能證明它不壞為止。常見的利用輸入進行攻擊的方式有緩衝區溢位攻擊、SQL Injection攻擊、Cross-Site Scripting(利用跨站指令碼攻擊)。
1
、緩衝區溢位攻擊 緩衝區溢位攻擊是根據程式在執行函數調用時,函數返回地址與函數的局部緩衝區都位於系統棧中這一原理,利用代碼中對局部變數邊界條件疏於檢查的漏洞,竄改函數的返回地址,引起程式異常或改變程式執行流程,進而獲得程式控制權的攻擊方法。 在圖2中,大家可以看到函數在棧中的具體分布情況。 (圖2)輸入溢出時在棧中的情況3所示。 (圖3)輸入溢出時在堆中的情況4所示。 (圖4) 下面通過一個具體的例子,來瞭解緩衝區溢位的基本原理。程式碼如下: void CopyStuff(string data) { char buffer[128]; strcpy(buffer,data); // do other stuff } 系統棧在調用CopyStuff()函數前記錄了參數data的值,然後將函數CopyStuff()調用結束後的返回地址壓棧,在儲存一些有用的寄存器後,將CopyStuff()函數中為局部變數分配的128個位元組壓棧。 這樣在調用函數時,如果傳入的字串長度是8個位元組,那麼此次的調用不會引起任何異常,並且通過參數data所傳入的字串被複製到了緩衝區buffer中。但是,如果調用CopyStuff()函數的是一個不懷好意的人,他完全可以傳入一個超出128個位元組長的字串。這時,CopyStuff()函數在調用strcpy()前,並不會對data的長度做任何檢查。這樣,通過參數data所傳入的字串會被strcpy()函數複製到從buffer開始的系統棧中,而超出128個位元組部分會沿著系統棧繼續向高位地址的方向延伸。如果該字串足夠長的話,字串內容就會覆蓋棧中最為關鍵的資訊——返回地址。CopyStuff()函數執行完畢後,就再也找不到正確的返回地址了。 緩衝區溢位意味著災難的降臨。但具體是何種災難,還要看調用者傳入的字串內容而定。如果比較幸運,覆蓋返回地址的資料只是些不能訪問的非法地址,程式就會在函數返回時異常終止,並報“非法訪問”的錯誤。如果不太幸運,覆蓋返回地址的資料指向了某個合法的代碼地址,程式就會產生難以預期的運行結果。可能的情況就是,程式總處於不穩定的狀態,但又很難確定究竟是哪裡出了毛病。如果非常不走運,駭客利用了緩衝區溢位將一段攻擊代碼放入系統棧中,並將函數的返回地址改為指向攻擊代碼的地址。那麼在函數返回時,系統就會自動執行駭客的攻擊代碼,駭客就可以擷取系統的管理員權限,進而刪除硬碟資料、隨心所欲地控制電腦了。 讓我們來看一下針對剛才的代碼所可能會發生的駭客攻擊情況。有經驗的駭客傳入CopyStuff()函數的字串是一段用機器指令編寫的攻擊代碼,該字串的長度超過buffer的長度。在剛好覆蓋返回地址的位置,駭客填寫的資料正是攻擊代碼的起始地址,這時系統棧被完全篡改了。CopyStuff()函數一執行完畢,系統就會自動跳轉到攻擊代碼的起始位置,開始執行駭客事先編排好的攻擊代碼。這樣,系統就喪失了抵抗能力,駭客可以為所欲為了。 瞭解了緩衝區溢位問題的原理之後,可以知道解決緩衝區溢位問題的方法其實並不複雜。只要我們在編程時注意檢查參數的合法性,不要隨意使用未知長度的字串,並避免使用類似strcpy()、strcat()、sprintf()這樣不安全的庫函數,就可以有效地防範緩衝區溢位攻擊了。 此外,微軟新一代的應用開發環境Visual Studio.NET也為應用軟體開發提供了更好的安全性支援。拿緩衝區溢位來說,Visual Studio.NET就提供了兩種解決方案。 首先,使用Visual C++.NET編譯C++語言程式時,可以開啟“/GS”緩衝區安全檢查選項。“/GS”選項的作用是:對於容易發生緩衝區溢位的函數,編譯器將修改函數的可執行代碼。在進入函數時,把返回地址和一個模組載入時隨機產生的安全Cookie進行異或運算,並儲存運算結果;在退出函數時,通過儲存的運算結果和安全Cookie檢驗返回地址的正確性。如果發現返回地址已被改寫,則表明已經發生了緩衝區溢位,這時系統將報告錯誤,並終止程式的執行。 其次,可以使用.NET提供的Managed 程式碼來編寫應用程式。.NET公用語言運行庫為Managed 程式碼提供了強大的安全檢查和安全保障機制,使用託管擴充編寫的C++語言程式具備數組邊界檢查和自動記憶體回收等安全特性,可以有效解決緩衝區溢位問題。
2
、SQL Injection
攻擊 SQL 嵌入攻擊是一種常見的Web攻擊方式,其產生的根源是程式員在使用服務端代碼訪問資料庫時,沒有對使用者輸入資訊的合法性進行檢查。下面是一段用C#語言編寫的代碼,我們來看一下針對此段代碼SQL 嵌入攻擊是如何進行的。
string Status = "No";
string sqlstring ="";
try {
SqlConnection sql= new SqlConnection(
@"data source=localhost;" +
"user id=sa;password=password;");
sql.Open();
sqlstring="SELECT *" +
" FROM OrderDetail WHERE ID='" + Id + "'";
SqlCommand cmd = new SqlCommand(sqlstring,sql);
if ((int)cmd.ExecuteScalar() != 0)
Status = "Yes";
} catch (Exception e) {
Status = e.ToString();
}
Good Guy
:
ID: 518
SELECT *
FROM OrderDetail
WHERE ID=‘518'
Not so Good Guy
:
ID: 518' or 1=1 --
SELECT *
FROM OrderDetail
WHERE ID= ‘518' or 1=1 -- '
Really Bad Guy
:
ID: 518'
drop table orders --
SELECT *
FROM OrderDetail
WHERE ID= ‘518' drop table orders -- '
Downright Evil Guy
:
ID: 518'
exec xp_cmdshell(‘
fdisk.exe’
) --
SELECT *
FROM OrderDetail
WHERE ID= ‘518' exec xp_cmdshell('fdisk.exe') -- '
其實,防範SQL嵌入攻擊最重要的一點就是牢記不要相信使用者輸入的資料內容,對所有的輸入都要進行驗證。另外,可以使用parameterized query來預防SQL嵌入攻擊的發生,對於上述代碼的改進方法如下:
SqlDataAdapter myCommand =
new SqlDataAdapter("SELECT * FROM OrderDetail WHERE ID = @id", conn);
SqlParameter parm =
myCommand.SelectCommand.Parameters.Add("@id",SqlDbType.VarChar, 11);
parm.Value = Id.Text;
3
、Cross Site Scripting 這是在網上最常見的一個漏洞,是由於伺服器端做得不好,而導致用戶端出現安全問題。其最根本的原因還是沒有對使用者的輸入進行有效檢查。
六、總結 開發安全的軟體、建設安全的系統必須遵循的原則包括: 1、建立面向安全的過程管理機制; 2、制定產品的安全目標; 3、將安全視作產品的一個重要特性; 4、從錯誤中吸取經驗教訓; 5、只授予使用者必要的使用許可權; 6、假設外界環境是不安全的; 7、為失敗做好準備; 8、使用安全的預設設定; 9、安全功能不等於軟體的安全; 10、不要把系統安全建立在攻擊者對系統不瞭解的假設之上。