MSDN 首頁 

程式碼的檢閱

發佈日期: 2004 年 5 月 28 日
本頁內容
本單元內容本單元內容
目標目標
適用於適用於
如何使用本單元如何使用本單元
FxCopFxCop
執行文字搜尋執行文字搜尋
跨網站指令碼 (XSS)跨網站指令碼 (XSS)
SQL 注入SQL 注入
緩衝區溢位緩衝區溢位
Managed 程式碼Managed 程式碼
程式碼存取安全性程式碼存取安全性
Unmanaged 程式碼Unmanaged 程式碼
ASP.NET 網頁及控制項ASP.NET 網頁及控制項
Web 服務Web 服務
服務元件服務元件
遠端服務遠端服務
資料存取程式碼資料存取程式碼
總結總結
其他資源其他資源

本單元內容

安全性程式碼的檢閱重點在於識別可能造成安全性問題與事件的程式設計技巧和弱點。即使需要很多時間,但是程式碼的檢閱卻是專案開發週期中必須定期採取的步驟,因為在開發的期間修正安全性瑕疵所需的成本和人力,比以後在專案開發或維護的期間再修正要少很多。

本單元將協助您檢閱利用 Microsoft .NET Framework 建置的 Managed ASP.NET Web 應用程式碼。本單元的組織是以功能區域劃分,其中各節包含廣泛的問題清單,來提供您程式碼的檢閱程序之方法和架構。

回到頁首回到頁首

目標

透過本單元即可:

針對執行程式碼的檢閱以及 ASP.NET 安全性稽核,建立一套方法和架構。

在程式碼中找出跨網站指令碼的弱點。

在程式碼中找出 SQL 注入攻擊的弱點。

在程式碼中找出潛在緩衝區溢位的弱點。

透過一系列廣泛的安全性問答,快速找出安全性的漏洞。

評估特定 .NET Framework 技術的安全性問題。

識別出讓惡意的使用者能發動攻擊的不良程式設計技巧。

瞭解如何使用 FxCop、文字搜尋,以及 ILDASM,來分析原始程式碼和可用的 .NET 組件。

回到頁首回到頁首

適用於

本單元適用於下列產品及技術:

Microsoft Windows Server 2000 及 2003

Microsoft .NET Framework 1.1 及 ASP.NET 1.1

Microsoft SQL Server 2000

回到頁首回到頁首

如何使用本單元

使用本單元來建立或擴充現有程式碼的檢閱程序。您必須確保程式碼的檢閱為開發週期整體的一部份,並瞭解其效率只能與所配置資源和預算的數量成比例。

檢閱的目標是要在部署程式碼之前,盡可能識別出所有潛在的安全性弱點,因為在開發的期間修正安全性瑕疵所需的成本和人力,比以後在產品開發週期再修正要少很多。

若要充分瞭解本單元:

請使用本單元 Part III 中同系列的安全保護單元。參考這些單元即可獲得其他關於本單元所略述之檢閱問題的詳細資訊。

單元 6<.NET 安全性概觀

單元 7<建置安全的組件

單元 8<程式碼存取安全性實務

單元 9<配合 ASP.NET 使用程式碼存取安全性

單元 10<建置安全的 ASP.NET 網頁和控制項

單元 11<建置安全的服務元件

單元 12<建置安全的 Web 服務>

單元 13<建置安全的遠端元件

單元 14<建置安全的資料存取

使用同系列的檢查清單:

檢查清單:架構及設計檢閱

檢查清單:保障 ASP.NET 的安全

檢查清單:保障 Web 服務的安全

檢查清單:保障 Enterprise Services 的安全

檢查清單:保障遠端服務的安全

檢查清單:保障資料存取的安全

回到頁首回到頁首

FxCop

透過 FxCop 分析工具來執行已編譯的組件,是啟動檢閱程序的理想方式。該工具能分析二進位的組件 (並非原始程式碼),來確保它們符合 .NET Framework 設計指南,它可以從 MSDN 取得。它還會檢查組件是否擁有增強名稱,這些名稱能夠防止竄改以及提供其他的安全性優勢。該工具原本就有一組預先定義好的規則,但是您可以自訂和延伸它們。

有關其他詳細資訊,請參閱下列資源:

若要下載 FxCop 工具,請參閱 http://www.gotdotnet.com/team/libraries/default.aspx

若要取得該工具的輔助與支援,請參閱 http://www.gotdotnet.com/community/messageboard/MessageBoard.aspx?ID=234

如需 FxCop 所檢查之安全性規則的清單,請參閱 http://www.gotdotnet.com/team/libraries/FxCopRules/SecurityRules.aspx

如需 .NET Framework 設計指南,請參閱 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconnetframeworkdesignguidelines.asp

回到頁首回到頁首

執行文字搜尋

為了使檢閱的程序更有效率,請熟悉可用來在檔案中找出字串的文字搜尋工具。此類型的工具可以讓您快速找出有弱點的程式碼。本單元後面所提出的許多檢閱問題都表示在尋找特定的弱點時,要搜尋的最佳字串。

您可能已擁有偏好的搜尋工具。如果沒有的話,您可以使用 Visual Studio .NET 中的檔案中尋找功能,或是 Findstr 命令列工具,它包含在 Microsoft Windows 作業系統中。

注意 如果在 Windows Explorer 使用 Windows XP 的 [搜尋] 工具,並使用 [在檔案中的單字或片語] 選項,請檢查是否安裝了最新的 Windows XP Service Pack,不然搜尋的工作就會失敗。如需詳細資訊,請參閱 Microsoft 知識庫文件 309173《Using the "A Word or Phrase in the File" Search Criterion May Not Work (英文)》。

搜尋固定寫在程式碼中的字串

在對原始程式碼執行詳細的逐列分析之前,請對整個程式碼基礎進行快速的搜尋,來識別固定寫在程式碼中的密碼、帳戶名稱,以及資料庫連線字串。掃描程式碼,並搜尋常見的字串模式,例如下列:key、secret、password、pwd,以及 connectionstring。
例如,若要在應用程式的 Web 目錄中搜尋「password」字串,請在命令提示號下使用 Findstr 工具,如下所示:

findstr /S /M /I /d:c:\projects\yourweb "password" *.*

Findstr 使用下列命命列參數:

/S — 包含子目錄。

/M — 僅列出檔案名稱。

/I — 使用不區分大小寫的搜尋。

/D:dir — 搜尋用分號分隔的目錄清單。如果您想要搜尋的檔案路徑包含空白,請將路徑包括在雙引號內。

自動化 Findstr

您可以用常見的搜尋字串來建立文字檔案。Findstr 然後就可以從文字檔案中讀取欲搜尋的字串,如下所示。在包含 .aspx 檔案的目錄中執行下列命令。

findstr /N /G:SearchStrings.txt *.aspx

/N 當找到符合處時,會印出對應的行號。/G 顯示包含搜尋字串的檔案。在本範例中,對所有 ASP.NET 網頁 (*.aspx) 都會進行搜尋包含在 SearchStrings.txt 內的字串。

ILDASM

您還可以使用 Findstr 命令連同 ildasm.exe 公用程式,來針對二進位的組件搜尋固定寫在程式碼中的字串。下列命令使用 ildasm.exe 來搜尋 ldstr 中間語言敘述,而它能夠識別字串常數。請注意下面所示的輸出是如何暴露固定寫在程式碼中的資料庫連線,以及眾所皆知的 sa 帳戶的密碼。

Ildasm.exe secureapp.dll /text | findstr ldstr
IL_000c:  ldstr      "RegisterUser"
IL_0027:  ldstr      "@userName"
IL_0046:  ldstr      "@passwordHash"
IL_0065:  ldstr      "@salt"
IL_008b:  ldstr      "Exception adding account. "
IL_000e:  ldstr      "LookupUser"
IL_0027:  ldstr      "@userName"
IL_007d:  ldstr      "SHA1"
IL_0097:  ldstr      "Exeception verifying password. "
IL_0009:  ldstr      "SHA1"
IL_003e:  ldstr      "Logon successful: User is authenticated"
IL_0050:  ldstr      "Invalid username or password"
IL_0001:  ldstr      "Server=AppServer;database=users; username='sa'  
password=password"

注意 Ildasm.exe 位於 \Program Files\Microsoft Visual Studio .NET 2003\SDK\v1.1\bin 資料夾中。如需所支援之命令列引數的詳細資訊,請執行 ildasm.exe /?

回到頁首回到頁首

跨網站指令碼 (XSS)

當程式碼在傳回用戶端的輸出 HTML 串流中使用輸入參數時,它就容易遭受跨網站指令碼 (Cross-Site Scripting,XSS,也稱為 CSS) 的攻擊。進行程式碼的檢閱之前,您可以先執行一項簡單的測試,來檢查應用程式是否易遭 XSS 的攻擊。搜尋會將使用者輸入資訊傳回瀏覽器的網頁。

XSS 漏洞是由於對使用者輸入的資料付予太高信任度而造成的。例如,應用程式可能希望使用者輸入價格,但是攻擊者卻包括了價格以及一些 HTML 和 JavaScript。因此,您一定要確定來自不可信任之來源的資料會經過驗證。在檢閱程式碼時,一定要問:「此資料是否已驗證過?」在 ASP.NET 應用程式中保存一份所有進入點的清單,例如,HTTP 標頭、查詢字串、表單資料等等,並確定所有輸入已在某個點上檢查其正確性。請勿測試是否有不正確的輸入值,因為該方法係假設您已知道所有潛在的危險輸入。在 ASP.NET 應用程式中,檢查該項資料是否正確的最常用方法,是使用規則運算式。

您可以在表單欄位中鍵入如「XYZ」的文字並測試其輸出,來執行簡單的測試。如果瀏覽器顯示了「XYZ」,或者如果在檢視 HTML 的原始程式碼時看到了「XYZ」,那麼 Web 應用程式就容易遭受 XSS 的攻擊。如果想要看到更動態的資訊,請注入 <script>alert('hello');</script>。此種技巧可能無法在所有情況下都正常運作,因為它決定於如何使用輸入來產生輸出。

下列程序能協助您識別常見的 XSS 弱點:

識別會將輸入輸出的程式碼

識別有潛在危險的 HTML 標籤和屬性

識別會處理 URL 的程式碼

檢查輸出是否有編碼

檢查是否執行正確的字元編碼

檢查 validateRequest 屬性

檢查 HttpOnly Cookie 選項

檢查 <frame> 安全性屬性

檢查 innerText 和 innerHTML 屬性的使用

識別會將輸入輸出的程式碼

在瀏覽器中檢視網頁輸出的原始程式碼,來瞭解程式碼是否置於屬性內。如果是的話,請注入下列程式碼,並重新測試來檢視其輸出。

"onmouseover= alert('hello');"

開發人員常用的一項技術是篩選 < > 字元。如果您檢閱的程式碼會篩選這些字元,那麼請改用下列程式碼來測試:

&{alert('hello');}

如果程式碼不會篩選這些字元,那麼您可以利用下列指令碼來測試程式碼:

<script>alert(document.cookie);</script>;

在使用此指令碼之前,您可能必須先關閉一個標籤,如下所示。

"></a><script>alert(document.cookie);</script>

搜尋「.Write」

在整個 .aspx 原始程式碼中,以及您已為應用程式開發的其他任何組件所包含的程式碼中,搜尋「.Write」字串。此舉會找出有 Response.Write 的位置,以及可能透過回應物件變數 (例如下面所示的程式碼) 產生輸出的任何內部常式。

public void WriteOutput(Response respObj)
{
respObj.Write(Request.Form["someField"]);
}

您還應該在 .aspx 原始程式碼中搜尋「<%=」字串,此程式碼還可以用來寫入輸出,如下所示:

<%=myVariable %>

下表顯示一些常見的狀況,其中 Response.Write 是與輸入欄位一起使用。

[表 21.1] 輸入的可能來源

輸入來源範例

表單欄位

 Response.Write(name.Text);
Response.Write(Request.Form["name"]); 

QueryString

 Response.Write(Request.QueryString["name"]); 

Cookies

Response.Write(  
Request.Cookies["name"].Values["name"]);

工作階段和應用程式變數

Response.Write(Session["name"]);
Response.Write(Application["name"]);

資料庫和資料儲存區

SqlDataReader reader = cmd.ExecuteReader();
Response.Write(reader.GetString(1));

識別有潛在危險的 HTML 標籤和屬性

雖然並不詳盡,但是下列常用的 HTML 標籤可以讓惡意使用者注入指令碼:

<applet>

<body>

<embed>

<frame>

<script>

<frameset>

<html>

<iframe>

<img>

<style>

<layer>

<ilayer>

<meta>

<object>

HTML 屬性 (例如,srclowsrcstyle,以及href) 可以連同上述標籤一起使用,來造成 XSS 攻擊。

例如,<img> 標籤的 src 屬性可能是注入的來源,如下列範例所示。

<IMG SRC="javascript:alert('hello');">
<IMG SRC="java&#010;script:alert('hello');">
<IMG SRC="java&#X0A;script:alert('hello');">

藉由變更 MIME 類型,<style> 標籤也可能是注入的來源,如下所示。

<style TYPE="text/javascript">
alert('hello');
</style>

檢查程式碼是否有進行輸入的安全性過濾,以找出某些已知危險的字元。請勿僅依賴此種方法,因為惡意的使用者通常可以找到另一種表示法,來通過您的檢驗。您的程式碼應該驗證輸入是否為已知的安全輸入。下表顯示用來表示一些常見字元的各種方式:

[表 21.2] 字元表示法

字元十進位十六進位HTML 字元集Unicode

" (雙引號)

 

 
&#34;

&#x22;

 &quot;

\u0022

' (單引號)

&#39;

&#x27;

&apos;

\u0027

& (ampersand)

&#38;

&#x26

&#38;

\u0026

< (小於)

&#60

&#x3C;

&#60;

\u003c

> (大於)

&#62

&#x3E;

&#62;

\u003e

識別會處理 URL 的程式碼

會處理 URL 的程式碼可能會有安全性弱點。檢閱程式碼看它是否有弱點,而容易遭受下列常見的攻擊:

如果 Web 伺服器未用最新的安全性補充程式更新,那麼它就可能容易遭受目錄周遊和雙斜線的攻擊,例如:

http://www.YourWebServer.com/..%255%../winnt
http://www.YourWebServer.com/..%255%..//somedirectory

如果您的程式碼會篩選「/」,攻擊者就可以利用同一字元的另一種表示法,來輕易地略過該篩選器。例如,/ 之過長的 UTF–8 表示法是 %c0f%af,而且它可以在下列 URL 中使用:

http://www.YourWebServer.com/..%c0f%af../winnt

如果程式碼能處理查詢字串的輸入,請檢查它是否會限制輸入的資料,並執行界限檢查。如果攻擊者透過查詢字串參數,來傳遞極大量的資料,請檢查程式碼是否會有安全性弱點。

http://www.YourWebServer.com/test.aspx?var=InjectHugeAmountOfDataHere

檢查輸出是否有編碼

雖然這不能取代檢查輸入的格式是否完整且正確,但是您應該檢查是否用 HtmlEncode 來為所有包含輸入之 HTML 的輸出編碼。此外,還要檢查 UrlEncode 是否用來將 URL 字串編碼。輸入資料可能得自查詢字串、表單欄位、Cookie、HTTP 標頭,以及從資料庫讀取的輸入 (特別是當資料庫是與其他應用程式共用時)。透過將資料編碼,您就能防止瀏覽器將 HTML 視為可執行的指令碼。

檢查字元編碼是否正確

若要協助防止攻擊者利用標準與多位元組的逸出序列來欺騙輸入的驗證常式,請檢查字元編碼是否已正確設定,來限制輸入表示的方式。

請檢查應用程式的 Web.config 檔案是否已將 <globalization> 元素設定的 requestEncoding responseEncoding 屬性,設定為如下所示。

<configuration>
<system.web>
<globalization 
requestEncoding="ISO-8859-1"
responseEncoding="ISO-8859-1"/>
</system.web>
</configuration>

字元編碼也可以在網頁的層級,利用 <meta> 標籤或 ResponseEncoding 網頁層級的屬性來設定,如下所示。

<% @ Page ResponseEncoding="ISO-8859-1" %>

如需詳細資訊,請參閱單元 10<建置安全的 ASP.NET 網頁和控制項>。

檢查 validateRequest 屬性。

利用 .NET Framework 1.1 版建置的 Web 應用程式,會執行輸入篩選來消除有潛在惡意的輸入,例如內嵌式指令碼。請勿依賴此項功能,但可使用它來執行深入的防護。檢查設定檔案中的 <pages> 元素,來確認已將 validateRequest 屬性設定為 true。此屬性也可以設定為網頁層級的屬性。掃描 .aspx 原始檔案找出 validateRequest,並對任何網頁檢查它並未設定為 false

檢查 HttpOnly Cookie 選項

Internet Explorer 6 SP 1 能夠支援新的 HttpOnly Cookie 屬性,它能防止用戶端的指令碼存取 document.cookie 屬性中的 Cookie。反之,會傳回空的字串。每當使用者瀏覽至目前網域中的網站時,這個 Cookie 仍會傳送至伺服器。如需詳細資訊,請參閱單元 10<建置安全的 ASP.NET 網頁和控制項>中的「跨網站指令碼」一節。

檢查 <frame> 安全性屬性

Internet Explorer 6 與更新的版本在 <frame> <iframe> 元素上,能支援新的 security 屬性。您可以使用 security 屬性,對個別的 frame iframe 套用使用者的受限制網站 Internet Explorer 安全性區域設定。如需詳細資訊,請參閱單元 10<建置安全的 ASP.NET 網頁和控制項>中的「跨網站指令碼」一節。

檢查 innerText 和 innerHTML 屬性的使用

如果建立了具不可信任輸入的網頁,請確認您使用的是 innerText 屬性而不是 innerHTMLinnerText 屬性可以安全地呈現內容,並確保指令碼不會執行。

其他資訊

如需有關 XSS 的詳細資訊,請參閱下列文章:

《CSS Quick Start: What Customers Can Do to Protect Themselves from Cross-Site Scripting (英文)》,網址為:http://www.microsoft.com/technet/security/news/crsstQS.asp

《CSS Overview (英文)》,網址為:http://www.microsoft.com/technet/security/news/csoverv.asp

Microsoft 知識庫文件 252985《How To: Prevent Cross-Site Scripting Security Issues (英文)》。

《CERT Advisory CA–2000–02, Malicious HTML Tags Embedded in Client Web Requests (英文)》,放在 CERT/CC 網站上,網址為:http://www.cert.org/advisories/CA-2000-02.html

《Understanding Malicious Content Mitigation for Web Developers (英文)》,放在 CERT/CC 網站上,網址為:http://www.cert.org/tech_tips/malicious_code_mitigation.html

回到頁首回到頁首

SQL 注入

當程式碼使用輸入參數來建構 SQL 敘述時,它就容易遭受 SQL 注入攻擊。就如 XSS 漏洞一樣,SQL 注入攻擊是由於對使用者輸入的資料付予太高信任度且沒有驗證輸入是否正確與且格式完整而造成的。

下列程序能協助您找出容易遭受 SQL 注入攻擊的弱點:

1.

尋找存取資料庫的程式碼
掃描以找出 SqlCommand、OleDbCommand,或 OdbcCommand。

2.

檢查程式碼是否使用參數化的預存程序
預存程序無法獨自防止 SQL 注入攻擊。檢查程式碼是否使用參數化的預存程序。檢查程式碼是否使用具型別的參數物件,例如:SqlParameterOleDbParameter,或 OdbcParameter。下列範例展示 SqlParameter 的使用:

SqlDataAdapter myCommand = new SqlDataAdapter("spLogin", conn);
myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
SqlParameter parm = myCommand.SelectCommand.Parameters.Add(
"@userName", SqlDbType.VarChar,12);
parm.Value=txtUid.Text;

具型別的 SQL 參數會檢查輸入的型別和長度,並確定將 userName 輸入值視為文字值,而不是資料庫中的可執行程式碼。

3.

檢查程式碼是否在 SQL 敘述中使用參數
如果未使用預存程序,請檢查程式碼是否在它建構的 SQL 敘述中使用參數,如下列範例所示:

select status from Users where UserName=@userName

檢查使用下列是否並使用,其中輸入是利用字串連接來直接建構可執行的 SQL 敘述。

string sql = "select status from Users where UserName='" 
+ txtUserName.Text + "'";

4.

檢查程式碼是否會嘗試篩選輸入
一種常用的方法是開發篩選常式,來將脫離字元加入對 SQL 有特殊意義的字元中。這是一種不安全的方法,因為字元表示法方面的問題,所以不應該依賴它。

回到頁首回到頁首

緩衝區溢位

在檢閱程式碼以找出緩衝區溢位時,請將檢閱工作專注於透過 P/Invoke 或 COM interop 層,來呼叫 Unmanaged 程式碼的程式碼。Managed 程式碼本身顯然比較不易遭受緩衝區溢位的攻擊,因為在存取陣列時,會自動檢查陣列的界限。一旦呼叫 Win32 DLL 或 COM 物件之後,您就應該詳細地檢查該 API 呼叫。

下列程序能協助您找出容易遭受緩衝區溢位攻擊的弱點:

1.

找出對 Unmanaged 程式碼的呼叫
掃描原始檔案以找出 System.Runtime.InteropServices,這是呼叫 Unmanaged 程式碼時所使用的命名空間名稱。

2.

檢查傳遞至 Unmanaged API 的字串參數
這些參數是緩衝區溢位的主要來源。檢查程式碼是否會檢查任何輸入字串的長度,來驗證它並未超過 API 所定義的限制。如果 Unmanaged API 接受了字元指標,此時除非能夠存取 Unmanaged 原始程式碼,否則您就無法知道所允許的最長字串長度。一項常見的弱點如下列程式碼片斷所示:

void SomeFunction( char *pszInput )
{
char szBuffer[10];
// 小心,沒有長度檢查。輸入會直接複製到緩衝區
// 應該要檢查長度或使用 strncpy。
strcpy(szBuffer, pszInput);
  . . .
}

注意 如果使用了 strncpy,緩衝區溢位仍然可能發生,因為它並不會檢查目的地字串中是否有足夠的空間,而且只會限制要複製的字元數目。

如果因為未擁有 Unmanaged 程式碼而不能加以檢查,請傳進很長的輸入字串與無效的引數,來嚴格測試該 API。

3.

檢查檔案路徑長度。
如果 Unmanaged API 接受了檔案名稱和路徑,請檢查包裝方法是否會檢查檔案名稱和路徑沒有超過 260 個字元。這是由 Win32 MAX_PATH 常數所定義的。請注意目錄名稱和登錄機碼最長可以有 248 個字元。

4.

檢查輸出字串。
檢查程式碼是否使用 StringBuilder,來接收從 Unmanaged API 傳回的字串。檢查 StringBuilder 的容量是否能存放 Unmanaged API 所傳回的最長字串,因為從 Unmanaged 程式碼傳回來的字串可以是任意的長度。

5.

檢查陣列的界限。
如果使用陣列來將輸入傳遞至 Unmanaged API,請檢查 Managed 包裝程式是否會驗證沒有超過陣列的容量。

6.

檢查 Unmanaged 程式碼是否有用 /GS 選項編譯。
如果您擁有 Unmanaged 程式碼,請使用 /GS 選項啟用堆疊偵測,來發覺某些種類的緩衝區溢位。

回到頁首回到頁首

Managed 程式碼

請使用這一節中的檢閱問題,來分析整個 Managed 原始程式碼基礎。不論組件的類型為何,檢閱的問題都能適用。這一節將協助您識別 Managed 程式碼常見的弱點。如需有關這一節所提出各項問題的詳細資訊,以及解說安全性弱點的程式碼範例,請參閱單元 7<建置安全的組件>。

如果 Managed 程式碼使用了明確的程式碼存取安全性的功能,請參閱本單元後面的<程式碼存取安全性>,來瞭解其他的檢閱點。下列檢閱的問題能協助您識別 Managed 程式碼的弱點:

您的類別設計是否安全?

您是否有建立執行緒?

您是否有使用序列化?

您是否有使用反射?

您是否有處理例外?

您是否有使用密碼編譯?

您是否有儲存機密?

您是否有使用委派?

類別的設計是否安全?

組件只能與其所包含的類別和其他類型一樣地安全。下列問題能協助您檢閱類別設計的安全性:

是否限制類型和成員的可見度?
檢閱任何標示為 public 的類型和成員,並檢查它是否是組件公開介面的一部份。

非基底的類別是否已密封?
如果不想從某類別推演過來,請使用 sealed 關鍵字,來防止程式碼遭有潛在惡意的子類別誤用。

對於公開的基底類別,您可以使用程式碼存取安全性的繼承需求,來限制可從該類別繼承過來的程式碼。這多少都是一種良好的防護。

您是否有使用屬性來公開欄位?
檢查類別是否不會直接公開欄位。使用屬性來公開非私密的欄位。此舉讓您能夠驗證輸入值,並套用其他的安全性檢查。

您是否有使用唯讀屬性?
驗證是否已有效地使用唯讀屬性。如果某欄位不是設計要加以設定的,則可以只提供 get 存取器來實作唯讀屬性。

您是否有使用虛擬的內部方法?
這些方法可以用能存取類別的其他組件加以覆蓋。使用宣告的檢查,或者如果 virtual 關鍵字不是必要的,就可將之移除。

您是否有實作 IDisposable?
如果是的話,請檢查在結束物件例項時是否會呼叫 Dispose 方法,來確保所有資源都已釋放。

您是否有建立執行緒?

多重執行緒程式碼容易有精細的與時間相關錯誤或賽跑情況,這些都可能造成安全性弱點。若要找出多重執行緒程式碼,請對原始程式碼搜尋文字 Thread,來識別建立新的 Thread 物件的位置,如下列程式碼片段所示:

Thread t = new Thread(new ThreadStart(someObject.SomeThreadStartMethod));

下列檢閱的問題能協助您識別執行緒的潛在弱點:

程式碼是否快取安全性檢查的結果?
如果程式碼快取了安全性檢查的結果,就會特別容易造成賽跑情況 (例如在靜態或全域變數中),然後再使用旗標來進行後續的安全性決策。

程式碼是否會模擬?
建立新執行緒的執行緒目前是否會模擬?新執行緒永遠會假設處理序層級的安全性內容,而不是現有執行緒的安全性內容。

程式碼是否包含靜態的類別建構函式?
如果有兩個或多個執行緒同時存取靜態類別建構函式,請檢查它們是否不容易遭受攻擊。如有必要,請將執行緒同步來防止發生這種情況。

您是否有將 Dispose 方法同步?
如果未將物件的 Dispose 方法同步,兩個執行緒就有可能對同一個物件執行 Dispose 方法。特別是如果清除程式碼釋放了 Unmanaged 資源處理常式 (例如,檔案、處理序,或執行緒處理),此舉就可以提出安全性問題。

您是否有使用序列化?

支援序列化的類別是標示為 SerializableAttribute,或是從 ISerializable 推演得到。若要找出支援序列化的類別,請執行文字搜尋,找出 Serializable 字串。然後針對下列問題來檢閱程式碼:

類別是否包含機密資料?
如果是的話,請檢查程式碼是否用 [NonSerialized] 屬性來標註機密資料,或實作 ISerializable 再控制要序列化哪些欄位,來防止機密資料遭序列化。

如果類別需要序列化機密資料,請檢閱如何保護該資料。請考慮先加密該資料。

類別是否會實作 ISerializable?
如果是的話,類別是否僅支援完全性認的呼叫者,例如它是安裝在並不包含 AllowPartiallyTrustedCallersAttribute 的強式命名組件中?如果類別能支援部分信任的呼叫者,請檢查 GetObjectData 方法的實作是否會利用適合的權限要求,來授權呼叫程式碼。使用 StrongNameIdentityPermission 要求來限制哪些組件才能將物件序列化是一項很好的技術。

類別是否會驗證資料串流?
如果程式碼包含了會接收序列化資料串流的方法,請檢查每一個欄位在從資料串流中讀出時,其是否已驗證。

您是否有使用反射?

若要協助找出會使用反射的程式碼,請搜尋 System.Reflection —。這是包含反射類型的命名空間。如果確實使用了反射,請檢閱下列問題來協助識別潛在的弱點:

您是否有動態地載入組件?
如果程式碼載入組件來建立物件例項並叫用類型,它是否會從輸入資料取得組件或類型的名稱?如果是的話,請檢查程式碼是否用權限要求加以保護,來確保所有呼叫程式碼皆已授權。例如,使用 StrongNameIdentity 權限要求或者要求完全的信任。

在程式執行時期是否動態地建立程式碼?
如果組件會機動產生程式碼來為呼叫者執行作業,請檢查呼叫者是否絕對不能影響到所產生的程式碼。例如,程式碼的產生是否依賴呼叫者所提供的輸入參數?這種情況應加以避免,或者如果有絕對的必要,請確定輸入已驗證,而且不能用來負面地影響程式碼的產生。

您是否有對其他類型使用反射?
如果是的話,請檢查唯有受信任的程式碼才能呼叫您。使用程式碼存取安全性權限的要求,來授權呼叫程式碼。

您是否有處理例外?

安全的例外處理對堅強的程式碼是必要的,它能夠確保會記錄足夠的例外詳細資訊,來協助進行問題診斷,並協助防止將內部的系統詳細資訊透露給用戶端。請檢閱下列問題,來協助識別潛在的例外處理的弱點:

您的程式碼是否會儘早失敗?
檢查程式碼以前是否會儘早失敗,來避免進行會消耗資源且不必要的處理。如果程式碼確實失敗了,請檢查造成的錯誤並不會讓使用者能夠通過安全性檢查,來執行有特殊權限的程式碼。

如何處理例外?
避免將系統或應用程式的詳細資訊透漏給呼叫者。例如,不要將呼叫堆疊傳回給使用者。用 try/catch 區塊來包裝可能產生例外的資源存取或作業。僅處理您知道如何處理的例外,並避免用一般的包裝函式來包裝特定的例外。

您是否有記錄例外的詳細資訊?
檢查例外的詳細資訊是否記錄在例外的來源處,來協助進行問題的診斷。

您是否有使用例外篩選器?
如果是的話,請瞭解呼叫堆疊中較高處篩選器中的程式碼,可以在 finally 區塊中的程式碼之前執行。請檢查您是否並不依賴 finally 區塊中的狀態變更,因為在例外篩選器執行之前,不會發生狀態變更。

如需例外篩選器弱點的範例,請參閱單元 7<建置安全的組件>中的「例外管理」。

您是否有使用密碼編譯?

如果是的話,請檢查程式碼是否並未實作其密碼編譯常式。反之,程式碼應該使用 System.Security.Cryptography 命名空間,或者使用 Win32 加密,例如資料保護應用程式設計介面 (DPAPI)。請檢閱下列問題,來協助識別潛在的密碼編譯相關的弱點:

您是否有使用對稱式加密?
如果是的話,當加密資料需要保存很長一段時間的時候,請檢查是否使用 Rijndael (現在稱為進階加密標準 [AES]) 或三重資料加密標準 (3DES)。僅使用較弱的 (但是較快速的) RC2 和 DES 演算法,來加密短生命週期的資料,例如工作階段資料。

您是否有使用最大的金鑰長度?
針對您使用的演算法使用最大的金鑰長度。較大的金鑰長度會使針對金鑰的攻擊更為困難,但是卻可能降低效能。

您是否有使用雜湊?
如果是的話,當您需要一個主體來證明其知道一項與您共用的機密時,請檢查您是否使用 MD5 和 SHA1。例如,挑戰回應式驗證系統是使用雜湊來證明,用戶端不必將密碼傳遞至伺服器,就能知道該密碼。請使用 HMACSHA1 連同訊息驗證碼 (MAC),此時您必須與用戶端共用金鑰。此舉可提供整合性檢查和某種程度的驗證。

您是否有為了密碼編譯的目的而產生隨機數字?
如果是的話,請檢查程式碼是否使用 System.Security.Cryptography.RNGCryptoServiceProvider 類別來產生隨機數字,而不是使用 Random 類別。此 Random 類別並不能產生不會重複出現或預測得到的真正隨機數字。

您是否有儲存機密?

如果組件儲存了機密,請檢閱其設計,來檢查是否有絕對的必須儲存該機密。如果必須儲存機密,請檢閱下列問提以便盡可能安全地執行:

您是否有將機密存放在記憶體中?
請勿將機密用純文字儲存在記憶體中,維持一段長時間。從儲存區擷取該機密、加以解密、使用,然後在儲存機密的空間中用零來代替。

您是否有將純文字密碼或 SQL 連線字串儲存在 Web.config 或 Machine.config 中?
請勿這麼做。請使用 aspnet_setreg.exe 在 <identity><processModel>,以及 <sessionState> 元素上,將加密的憑證儲存在登錄中。如需有關取得與使用 Aspnet_setreg.exe 的詳細資訊,請參閱 Microsoft 知識庫文件 329290《How To: Use the ASP.NET Utility to Encrypt Credentials and Session State (英文)》。

如何加密機密?
檢查程式碼是否使用 DPAPI 來加密連線字串和憑證。請勿將機密儲存在本機安全性授權 (LSA) 中,因為用來存取 LSA 的帳戶需要延伸的特殊權限。如需有關使用 DPAPI 的詳細資訊,請參閱《Microsoft patterns & practices Volume I, 建置安全的 ASP.NET 應用程式: 驗證、授權和安全通訊》的<How To>一節中的<How To:建立 DPAPI 程式庫>,網址為 http://www.microsoft.com/taiwan/msdn/books/ataglance/SecNetHT07.htm

您是否有將機密儲存在登錄中?
如果是的話,請檢查其是否先加密,如果其儲存在 HKEY_LOCAL_MACHINE 中的話,然後再用限制的 ACL 來保護安全。如果程式碼使用了 HKEY_CURRENT_USER,就不需要 ACL,因為此程式碼會自動限制於用相關使用者帳戶執行的處理序。

您是否有關心還原工程?
如果是的話,可以考慮模糊化工具。如需詳細資訊,請參閱 http://www.gotdotnet.com/team/csharp/tools/default.aspx 所列的模糊化工具清單。

注意 請勿依賴模糊化工具來隱藏機密資料。模糊化工具能使識別機密資料更困難,但卻不能解決此問題。

您是否有使用委派?

任何程式碼都可以將方法與委派建立起關聯。這種情況包括在比您的程式碼更低的信任層級上執行,且具潛在惡意的程式碼。

您是否有從不可信任的來源接受委派?
如果是的話,請檢查是否利用對 SecurityAction.PermitOnly 的安全性權限,來限制委派方法可供使用的程式碼存取權限。

在呼叫委派之前是否使用宣告?
避免發生這種情況,因為在呼叫委派程式碼之前,您並不知道它會做什麼事。

回到頁首回到頁首

程式碼存取安全性

所有 Managed 程式碼都有程式碼存取安全性權限的要求。當程式碼是在部分信任的環境裡使用時,以及當程式碼存取安全性原則並未將完全的信任授予您的程式碼或呼叫程式碼時,許多問題才會變得很明顯。

如需有關這一節所提出各項問題的詳細資訊,請參閱單元 8<程式碼存取安全性實務>。

請使用下列檢閱點,來檢查是否適當地且安全地使用程式碼存取安全性:

您是否有支援部分信任的呼叫者?

您是否有限制存取公開類型和成員?

您是否有使用宣告的安全性?

您是否有呼叫 Assert?

在應該使用權限要求時,是否使用它?

您是否有使用連結要求?

您是使用 Deny 還是 PermitOnly?

您是否有使用特別危險的權限?

您是否有用 /unsafe 選項來編譯?

您是否有支援部分信任的呼叫者?

如果程式碼能支援部分信任的呼叫者,它就更有可能遭受攻擊,結果執行廣泛且徹底之程式碼的檢視就更為重要。檢閱 Web 應用程式中 <trust> 層級的組態設定,來瞭解它是否是在部分信任的層級執行。如果是的話,為應用程式開發的組件就必須能夠支援部分信任的呼叫者。

下列問題能協助您識別具潛在弱點的區域:

組件是否以強式命名?
如果是的話,預設的安全性原則就能確保部分信任的呼叫者不能呼叫它。Common Language Runtime (CLR) 會發出對完全信任的隱含連結要求。如果組件不是以強式命名,除非您採取明確的步驟來限制呼叫者 (例如明確地要求完全的信任),否則認何程式碼都不能呼叫它。

注意 ASP.NET 應用程式所呼叫以強式命名的組件必須安裝在「全域組件快取」中。

您是否有使用 APTCA?
如果以強式命名的組件包含了 AllowPartiallyTrustedCallersAttribute,部分信任的呼叫者就可以呼叫程式碼。在此情況下,請檢查組件所執行的任何資源存取或其他有特殊權限的作業,是否用其他程式碼存取安全性要求來授權和保護。如果使用 .NET Framework 類別庫來存取資源,除非程式碼已使用 Assert 呼叫來防止堆疊巡迴,否則就會自動發出完整堆疊巡迴的要求,並且會授權呼叫程式碼,。

您是否有交出物件的參考?
請檢查方法是否傳回以及 ref 參數,來瞭解程式碼將物件參考傳回何處。請檢查部份信任的程式碼,是否不會交出對從組件 (需要完全信任的呼叫者) 取得物件的參考。

您是否有限制存取公開類型和成員?

您可以使用程式碼存取安全性識別身份的要求,來限制存取到公開的類型和成員。這是減少組件的攻擊面的一種很有用的方式。

您是否有利用識別身份要求來限制呼叫者?
如果有只想要由特定組件在特定應用程式裡使用的類別或結構,您就可以使用識別身份的要求,來限制呼叫者的範圍。例如,您可以使用對 StrongNameIdentityPermission 的要求,將呼叫者限制於已用私密金鑰 (對應於要求中的公開金鑰) 簽署的特定組件集。

您是否有使用繼承要求來限制梓類別?
如果您知道唯有特定的程式碼才應從基底類別繼承,請檢查該類別是否使用了具有 StrongNameIdentityPermission 的繼承要求。

您是否有使用宣告的安全性屬性?

宣告的安全性屬性可以用如 Permview.exe 等工具來顯示。此舉能大幅地協助組件的消費者和管理員瞭解程式碼的安全性需求。

您是否有要求最小的權限?
搜尋 .RequestMinimum 字串,瞭解程式碼是否使用權限的要求,來指定其最小的權限需求。 您應該完成這項工作,來清楚地記載組件的權限需求。

您是否有要求可選擇或者拒絕權限?
搜尋 .RequestOptional 和 .RequestRefuse 字串。如果使用這兩個動作中的任一個,來開發最少權限的程式碼,請明白程式碼將不再能呼叫以強式命名的組件,除非其已標示為 AllowPartiallyTrustedCallersAttribute

您是否有使用強制的安全性而不使用宣告的安全性?
有時候程式碼中的強制檢查是必要的,因為您必須套用邏輯,來決定要求哪些權限,或是因為在要求中您需要一個執行時期變數。如果不需要特定的邏輯,請考慮利用宣告的安全性來記載組件的權限需求。

您是否有將類別與成員層級的屬性混合?
請勿這麼做。成員屬性 (例如針對方法或屬性) 會用相同的安全性動作,來取代類別層級的屬性,並且不與其結合。

您是否有呼叫 Assert?

掃描程式碼找出 Assert 呼叫。此舉可能找到 Debug.Assert 的例項。尋找程式碼在何處對 CodeAccessPermission 物件呼叫 Assert。在宣告程式碼存取的權限時,您會將程式碼存取安全性權限要求的堆疊巡迴切短,此舉是一種危險的行為。您的程式碼會採取哪些步驟確保惡意的呼叫者不會利用宣告,來存取保護安全的資源或具特殊權限的作業?請檢閱下列問題:

您是否有使用要求、宣告模式?
請檢查程式碼在發出 Assert 之前是否發出了 Demand。程式碼在宣告更廣泛的權限 (例如 Unmanaged 程式碼的權限) 之前,應要求更精細的權限來授權呼叫者。

您是否有將 Assert 呼叫與 RevertAssert 配合?
請檢查每一次呼叫 Assert 都會配合一次呼叫 RevertAssert。當呼叫 Assert 的方法傳回時,就會隱含地將 Assert 移除,但是實用上卻最好在呼叫 Assert 之後,盡速明顯地呼叫 RevertAssert

您是否有減少宣告的期間?
請檢查是否僅宣告一項權限,維持一段所需最短的時間。例如,如果僅在呼叫另一個方法時才需要使用 Assert 呼叫,請檢查在該方法呼叫之後,是否立即呼叫 RevertAssert

在應該使用權限要求時,是否使用它?

程式碼永遠會在 .NET Framework 類別庫中,遭受權限要求的檢查,但是如果程式碼使用了明顯的權限要求,請檢查此動作是否已恰當地完成。對程式碼搜尋 .Demand 字串,來識別宣告的和強制的權限要求,然後再檢閱下列問題:

您是否有快取資料?
如果是的話,請檢查在存取快取的資料之前,程式碼是否發出了適合的權限要求。例如,如果資料是從檔案中取得,且想要確保已授權呼叫程式碼,來存取公開快取處的檔案,請在存取快取的資料之前,發出要求 FileIOPermission

您是否有公開自訂資源或特殊權限操作?
如果程式碼透過 Unmanaged 程式碼公開了自訂的資源或特殊權限的操作,請檢查它是否發出了適合的權限要求,而決定於資源的本性,此要求可能是內建的權限類型或是自訂的權限類型。

您是否有要求得夠快?
檢查在存取資源或執行具特殊權限的作業之前,是否已發出權限要求。請勿存取該資源,然後再授權呼叫者。

您是否有發出多餘的要求?
使用 .NET Framework 類別庫的程式碼會遭受到權限要求。程式碼就不必發出相同的要求。此舉會造成重複且浪費的堆疊巡迴。

您是否有使用連結要求?

連結要求與一般的要求不同,只會檢查立即的呼叫者。它們並不會執行完整的堆疊巡迴,結果使用連結要求的程式碼就會遭受誘惑攻擊。如需有關誘惑攻擊的詳細資訊,請參閱單元 8<程式碼存取安全性實務>中的「連結要求」。

對程式碼搜尋 .LinkDemand 字串,來識別使用連結要求的位置。只能宣告性地使用它們。一個範例如下列程式碼片斷所示:

[StrongNameIdentityPermission(SecurityAction.LinkDemand, 
PublicKey="00240000048...97e85d098615")]
public static void SomeOperation() {}

如需有關這一節所提出各項問題的詳細資訊,請參閱單元 8<程式碼存取安全性實務>中的「連結要求」。下列問題能協助您檢閱在程式碼中使用連結要求:

為何使用連結要求?
一種防禦的方法就是盡可能避免用連結要求。請勿只是為了改進效能以及消除完整的堆疊巡迴而使用它們。若與其他 Web 應用程式效能問題的成本比較起來 (例如網路延遲和資料庫存取),堆疊巡迴的成本很小。唯有在知道且能限制哪一個程式碼能呼叫程式碼時,連結要求才是安全的。

您是否有信任您的呼叫者?
當使用連結要求時,您必須依賴呼叫者來防止遭受誘惑攻擊。唯有在知道並能限制程式碼的直接呼叫者的確實數目時,連結要求才是安全的,此時您就可以信任那些呼叫者來授權其呼叫者。

您是否有呼叫用連結要求保護的程式碼?
如果是的話,程式碼是否會透過在程式碼的呼叫者中要求安全性權限,來提供授權。傳遞至方法的引數是否能一直傳遞至您呼叫的程式碼?如果是的話,它們是否可以惡意地影響您呼叫的程式碼?

在方法和類別的層級是否曾使用連結要求?
當您將連結要求加入方法時,它會覆蓋對類別的連結要求。檢查方法是否也包含類別層級的連結要求。

您是否有對未密封的類別使用連結要求?
連結要求不會藉由推演的類型而繼承,且在對推演的類型呼叫覆蓋的方法時,並不會使用。如果對需要用連結要求來保護的方法加以覆蓋,請將連結要求套用至覆蓋的方法。

您是否有使用連結要求來保護結構?
連結要求不會防止不信任的呼叫函式建構一個結構。這是因為不會為結構自動產生預設的建構函式,所以唯有在使用明顯的建構函式時,才能套用結構層級的連結要求。

您是否有使用明顯的介面?
搜尋 Interface 關鍵字來將其找出。如果是的話,請檢查方法的實作是否已標示為連結要求。如果它們是的話,請檢查介面的定義是否包含相同的連結要求。否則,呼叫者就有可能略過連結要求。

您是否有使用有潛在危險的權限?

請檢查下列權限類型是否僅授與給高度信任的程式碼。大部份程式碼沒有自己專用的權限類型,而使用一般的 SecurityPermission 類型。因此您應該仔細檢查會使用這些類型的程式碼,以確保風險降到最低。此外,您一定要有很好的理由才能使用這些權限。

[表 21.3] 危險的權限

權限說明

SecurityPermission.UnmanagedCode

程式碼可以 呼叫 Unmanaged 程式碼。

SecurityPermission.SkipVerification

不再需要驗證此組件中的程式碼是否為安全類型。

SecurityPermission.ControlEvidence

該程式碼可以為它自己提供證據,以供安全性原則評估之用。

SecurityPermission.ControlPolicy

程式碼可以檢視及更改原則。

SecurityPermission.SerializationFormatter

程式碼可以使用序列化。

SecurityPermission.ControlPrincipal

程式碼可以操作用於授權的主體物件。

ReflectionPermission.MemberAccess

程式碼可以透過反射來叫用某類型的私用成員。

SecurityPermission.ControlAppDomain

程式碼可以建立新的應用程式網域。

SecurityPermission.ControlDomainPolicy

程式碼可以變更網域原則。

編譯時是否使用 /unsafe 選項?

請使用 Visual Studio .NET 來檢查專案屬性,以查看 Allow Unsafe Code Blocks 是否已設定為 true。這會設定編譯器旗標 /unsafe ,以告訴編譯器程式碼中含有危險的區塊和要求,而且最小的使用權限 SkipVerification 已放在該組件中。

如果您編譯時使用了 /unsafe,請再次檢閱您為何需要這麼做。如果理由正當,請多花一些時間來檢閱原始碼,以找出可能的弱點。

回到頁首回到頁首

Unmanaged 程式碼

由於安全性風險增大,請特別注意會呼叫 Unmanaged 程式碼的程式碼,包括 Win32 DLL 和 COM 物件。Unmanaged 程式碼不是確實安全的類型,而且可能會引起緩衝區溢位。從 Unmanaged 程式碼存取資源時,並不會受到程式碼存取安全性的檢查。這是屬於 Managed 包裝函式類別的職責。.

一般來說,您不應該讓 Unmanaged 程式碼直接開放給部份信任的呼叫者。有關這一節所提出各項問題的詳細資訊,請參閱單元 7<建置安全的組件>中的「Unmanaged 程式碼」一節,以及單元 8<程式碼存取安全性實務>。

請使用下列檢閱性問題來檢驗 Unmanaged 程式碼之使用:

您是否有宣告 Unmanaged 程式碼的使用權限?

如果是,請在呼叫 Assert 方法之前,先檢查程式碼所要求的權限是否適當,以確保所有的呼叫者已獲授權存取資源或 Unmanaged 程式碼所開放的操作。例如,下列程式碼片段顯示如何要求一個自訂的「加密」權限,然後宣告 Unmanaged 程式碼的權限:

// 要求自訂 EncryptionPermission。
(new EncryptionPermission(
EncryptionPermissionFlag.Encrypt, storeFlag)).Demand();
// 宣告 Unmanaged 程式碼權限。
(new SecurityPermission(SecurityPermissionFlag.UnmanagedCode)).Assert();
// 現在使用 P/Invoke 來呼叫 Unmanaged DPAPI 函數。

若要取得更多詳細資訊,請參閱單元 8<程式碼存取安全性實務>中的「Assert 與 RevertAssert」。

您是否有使用 SuppressUnmanagedCodeAttribute?
當 Managed 程式碼呼叫 Unmanaged 程式碼時,此屬性會抑制對自動發出的 Unmanaged 程式碼權限之需求。如果 P/Invoke 方法或 COM interop 介面使用此屬性來加註,請確保所有指向 Unmanaged 程式碼呼叫的程式碼路徑已受到安全性權限要求的保護,來授權呼叫者。同時,請檢查此屬性是用於方法層級,而非類別層級。

注意 新增 SupressUnmanagedCodeSecurityAttribute 會把對 interop 階層發出的 UnmanagedCode 權限之隱含需求轉變為 LinkDemand。您的程式碼將容易受到誘惑攻擊。

Unmanaged 進入點公開可見嗎?
請檢查 Unmanaged 程式碼的進入點是否已標示為 private internal。呼叫者會被強制呼叫 Managed 包裝函式方法,以封裝 Unmanaged 程式碼。

您是否有加以保護以防止緩衝區溢位?
Unmanaged 程式碼易受輸入攻擊,如緩衝區溢位。Unmanaged 程式碼 API 應該檢查提供之參數的類型及長度。不過,您不能依賴此檢查,因為您可能沒有 Unmanaged 來源。因此,Managed 包裝函式程式碼必須嚴格地檢查輸入和輸出參數。如需詳細資訊,請參閱本指南的<緩衝區溢位>。

注意 所有套用到 C 及 C++ 之程式碼的檢閱規則和原則,都會套用到 Unmanaged 程式碼。

您是否有對列舉型別進行範圍檢查?
在傳遞任何列舉值到原始方法之前,請先檢查該值是否在範圍之內。

您是否有對 Unmanaged 程式碼方法使用命名慣例?
所有 Unmanaged 程式碼應該在包裝函式類別之內,例如:NativeMethodsUnsafeNativeMethodsSafeNativeMethods。您必須徹底檢閱在 UnsafeNativeMethods 之內的所有程式碼,以及傳遞給原始 API 以進行安全性攻擊的參數。

您是否有呼叫具有潛在危險的 API?
您應該能調整所有 Win32 API 呼叫的使用。具潛在危險的 API 有:

執行緒功能,會切換安全性內容。

存取權杖功能,可以進行變更或公開有關安全性權帳的資訊。

憑證管理功能,包括建立權杖的功能。

Crypto API 功能,可以加密及存取私密金鑰。

記憶體管理功能,可以讀取和寫入記憶體。

LSA 功能,可以存取系統機密。

回到頁首回到頁首

ASP.NET 網頁及控制項

請使用這一節所述的檢閱性問題來檢閱 ASP.NET 網頁和控制項。有關這一節所提出各項問題的詳細資訊,請參閱單元 10<建置安全的 ASP.NET 網頁和控制項>。

您是否有停用詳細的錯誤訊息?

您是否有停用追蹤?

您是否有驗證表單欄位的輸入?

您是否有遭受 XSS 攻擊?

您是否有驗證查詢字串及 Cookie 輸入?

您是否有依賴 HTTP 標頭來保障安全?

您是否有保障檢視狀態的安全?

您是否有防止 XSS?

您的 Global.asax 事件處理常式是否安全?

您是否有提供適當的授權?

您是否有停用詳細的錯誤訊息?

如果允許例外傳播超越應用程式界線,ASP.NET 便會傳回給呼叫者詳細的資訊。這些資訊包括完整的堆疊追蹤和其他有助於攻擊者的資訊。請檢查 <customErrors> 元素,並確認模式屬性已設定為 OnRemoteOnly

<customErrors mode="On" defaultRedirect="YourErrorPage.htm" />

您是否有停用追蹤?

追蹤資訊對攻擊者也是非常有幫助的。請檢查 <trace> 元素以確認已停用追蹤。

<trace enabled="false" localOnly="true" pageOutput="false" 
requestLimit="10" traceMode="SortByTime"/>

您是否有驗證表單欄位的輸入?

攻擊者可以透過公佈的表單欄位,將惡意的輸入傳遞至您的網頁和控制項。請檢查您是否已驗證所有表單欄位的輸入,包括隱藏的表單欄位。然後驗證欄位的類型、範圍、格式和長度。最後,使用下列問題來檢閱您 ASP.NET 的輸入處理情形:

您的輸入包括檔案名稱或檔案路徑?
通常您應該避免這種輸入方式,因為它是一種高風險的操作。為什麼您要使用者指定檔案的名稱或路徑,而不是由應用程式根據使用者識別身份來選擇位置?

如果您接受檔案名稱和路徑作為輸入,您的程式碼就容易受到正規化漏洞的攻擊。如果您必須接受使用者輸入的路徑,那麼請檢查是否已經過驗證為安全路徑而且正規化。請檢查程式碼是否已使用 System.IO.Path.GetFullPath

您是否有呼叫 MapPath?
如果您呼叫 MapPath 時並附加使用者提供的檔案名稱,那麼請檢查您的程式碼是否已使用 HttpRequest.MapPath 的替代值,以接受 bool 參數,可防止跨應用程式的對應。

try
{
string mappedPath = Request.MapPath( inputPath.Text, 
Request.ApplicationPath, false);
}
catch (HttpException)
{
// 嘗試跨應用程式對應。
}

如需詳細資訊,請參閱單元 10<建置安全的 ASP.NET 網頁和控制項>中的「使用 MapPath」一節。

如何驗證資料型別?
請檢查您的程式碼是否驗證資料的型別,包括從公佈的表單欄位及其他網路輸入 (如查詢字串) 表單所接收的資料。對於非字串資料,請檢查您的程式碼是否使用 .NET Framework 型別系統來執行型別檢查。您可以將輸入字串轉換成增強式型別物件,並擷取任何型別轉換例外。例如,如果一欄位包含日期資料,即可以使用它來建構一 System.DateTime 物件。如果它包含一個很久遠的時間,請使用 Int32.Parse,將它轉換成 System.Int32 物件,並擷取格式例外狀況。

如何驗證字串型別?
請檢查是否使用規則運算式來驗證輸入字串的長度,以及可接受的字元和圖樣 (Pattern) 組。您可以使用 RegularExpressionValidator 驗證控制項或直接使用 RegEx 類別。請不要搜尋無效的資料;只搜尋就您所知是正確的資訊格式。

您是否有使用驗證控制項?
如果您有使用驗證控制項,例如 RegularExpressionValidator、RequiredFieldValidator、CompareValidator、RangeValidator 或 CustomValidator,則請檢查是否未停用伺服器端的驗證,而且並未完全依賴用戶端的驗證。

您是否有依賴用戶端驗證?
請勿這麼做。僅將用戶端驗證用於改善使用者經驗即可。請檢查所有輸入是否已在伺服器驗證過。

您是否容易受到 XSS 攻擊?

請務必檢閱網頁是否有 XSS 弱點。如需詳細資訊,請參閱本單元前面的<跨網站指令碼 (Cross-Site Scripting,XSS)>。

您是否有驗證查詢字串及 Cookie 輸入?

請檢查程式碼是否驗證由 URL 查詢字串所傳遞的輸入欄位,以及從 Cookie 擷取的輸入欄位。若要找到易受攻擊的程式碼,請搜尋下列文字字串:

"Request.QueryString"

"Request.Cookies"

請使用具型別的物件來檢查輸入的型別、範圍、格式及長度是否已驗證過,就像對表單欄位使用規則運算式一樣 (請參閱先前的<您是否有驗證表單欄位的輸入?>一節)。此外,也需考慮源自使用者輸入的任何輸出之 HTML 或 URL 編碼,因為這會使任何可能引起 XSS 漏洞攻擊的無效結構反而變成有效。

您是否有保障檢視狀態的安全?

如果應用程式使用檢視狀態,那麼它可防止竄改嗎?請檢閱下列問題:

您是否有在應用程式層級啟用檢視狀態保護?
請檢查應用程式的 Machine.config 或 Web.config 檔案中 <pages> 元素的 enableViewState 屬性,以查看檢查狀態是否在應用程式層級被啟用。然後檢查 enableViewStateMac 是否設定為 "true" ,以確定它是可防止竄改的。

<pages enableViewState="true" enableViewStateMac="true" />

是否以每頁為基礎來覆寫檢視狀態保護?
請檢查位於網頁頂端之頁面層級的指示詞,以確認該頁面的檢視狀態已啟用。請尋找 enableViewStateMac 設定。如果找到,請檢查它是否設定為 "true"。如果沒找到 enableViewStateMac ,即設定為 true,則頁面會採用 Web.config 檔案中指定之應用程式層級的預設設定。如果您是藉由設定 enableViewState 為 "false",以停用該頁面的檢視狀態,則保護設定不會受影響。

您是否有在程式碼中覆寫檢視狀態保護?
請檢查程式碼是否並未藉由設定 Page.EnableViewStateMac 屬性為 false,來停用檢視狀態保護。唯有頁面未使用檢視狀態,這才是一項安全的設定。

Global.asax 事件處理常式是否安全?

Global.asax 檔案包含應用程式層級之事件的事件處理程式碼,該事件是由 ASP.NET 和 HTTP 模組產生的。請檢閱下列事件處理常式,以確保程式碼並未包含任何弱點:

Application_Start。此處的程式碼會在 ASP.NET 處理序帳戶 (而非模擬的使用者) 的安全性內容下執行。

Application_BeginRequest。此處的程式碼會在 ASP.NET 處理序帳戶 (或模擬的使用者) 的安全性內容下執行。

Application_EndRequest。如果需要修改外送 Cookie 的內容,例如,設定 Secure 位元或網域,請在Application_EndRequest 處進行設定。

Application_AuthenticateRequest。這會執行使用者驗證。

Application_Error。當此事件處理常式被呼叫,安全性內容會對 Windows 事件記錄檔之寫入有所影響。安全性內容可能是處理序帳戶或模擬的使用者。

protected void Session_End。此事件是在不確定的情況下被觸發,而且只有同處理序 (In-process) 的工作階段狀態模式會這樣。

您是否有提供適當的授權?

請檢閱下列問題以確認您的授權方法:

您是否有在限制和公用存取區域之間分割您的網站?
如果您的 Web 應用程式需要使用者完成驗證後,才可以存取特定的網頁,請檢查受限制的網頁是否放在不同於可公用存取之網頁的單獨目錄中。這樣您才可以設定受限制的目錄來要求 SSL?同時也可幫助您確保驗證 Cookie 不會經由使用 HTTP 的未加密工作階段而傳遞。

如何保護受限制網頁的存取?
如果您使用 Windows 驗證,您是否已在該網頁上設定 NTFS 權限 (或包含受限制網頁的資料夾),只允許經過授權的使用者存取?

您是否已設定 <authorization> 元素,來指定哪些使用者和使用者群組可存取特定的網頁?

如何保護網頁類別的存取?
是否已將主體使用權限要求加入至您的類別,以決定哪些使用者和使用者群組可以存取該類別?

您是否有使用 Server.Transfer?
如果您使用 Server.Transfer 來轉移使用者到其他的網頁,請確認目前驗證的使用者已獲授權存取目標網頁。如果您使用 Server.Transfer 轉移到使用者未獲授權的網頁以進行檢視,那麼該網頁仍會被處理。

Server.Transfer 會使用不同的模組來處理該網頁,而不會從伺服器另外提出要求,以強制執行授權。如果安全是目標網頁上的一項考量,就請不要使用 Server.Transfer ;而改用 HttpResponse.Redirect

回到頁首回到頁首

Web 服務

ASP.NET Web 服務分享許多與 ASP.NET Web 應用程式相同的功能。在您提出下列與 Web 服務相關的問題之前,請先以<ASP.NET 網頁及控制項>一節所列的問題來檢查您的 Web 服務。有關這一節所提出各項問題的詳細資訊,請參閱單元 12<建置安全的 Web 服務>。

您是否有公開限制的操作或資料?

如何授權呼叫者?

您是否有限制特殊權限操作?

您是否有使用自訂驗證?

您是否有驗證所有輸入?

您是否有驗證 SOAP 標頭?

您是否有公開限制的操作或資料?

如果 Web 服務公開了受限制的操作或資料,請檢查該服務是否驗證呼叫者。您可以使用平台驗證機制,如 NTLM、Kerberos、基本驗證或用戶端 X.509 憑證,或者您可以傳遞 SOAP 標頭中的驗證權杖。

如果您要傳遞驗證權杖,那麼可以利用「Web 服務增強功能」(Web Services Enhancements,WSE),以符合新興 WS-Security 標準的方式來使用 SOAP 標頭。

如何授權呼叫者?

請選擇適當的授權方案,這些方案可由 .NET Framework (例如,URL 授權、檔案授權和「.NET 角色」) 或平台選項 (如「檔案 ACL」) 提供。

您是否有限制特殊權限操作?

程式碼存取安全性原則的信任等級,可決定 Web 服務可存取的資源類型。請檢查 Machine.config 或 Web.config 中 <trust> 元素的設定。

您是否有使用自訂驗證?

請使用「Web 服務增強功能」(WSE) 所提供的功能,而不要建立自己的驗證方式。

您是否有驗證所有輸入?

如果接收到的輸入來自於目前信任界線之外的來源,請先檢查公開展示的所有 Web 方法是否驗證其輸入參數,然後才可以使用它們或將它們傳遞到下游元件或資料庫。

您是否有驗證 SOAP 標頭?

如果您在應用程式中使用自訂的 SOAP 標頭,請檢查資訊是否未被竄改或未被重新執行。以數位方式簽署標頭資訊可確保資訊未被竄改。您可以使用 WSE 以標準的方式來簽署 Web 服務郵件。

請檢查 SoapException SoapHeaderException 物件是否用來適當地處理錯誤,及提供用戶端基本的必要資訊。請確認例外是否被正確地記錄,以供疑難排解之用。

回到頁首回到頁首

服務元件

這一節會指出您應該考慮的檢閱要點,來檢閱 Enterprise Services 應用程式內所用的服務元件。有關這一節所提出各項問題的詳細資訊,請參閱單元 11<建置安全的服務元件>。

您是否有使用組件層級中繼資料?

您是否有防止匿名存取?

您是否有使用限制的模擬層級?

您是否有使用角色為基礎的安全性?

您是否有使用方法層級授權?

您是否有使用物件建構函式字串?

您是否有在中介層 (Middle Tier) 進行稽核?

您是否有使用組件層級中繼資料?

請檢查是否使用組件層級中繼資料,來定義 Enterprise Services 安全性設定。使用 assemblyinfo.cs 檔案,然後使用屬性來定義驗證和授權設定。這樣可確保已在系統管理時正確地建立這些設定。雖然系統管理員可以覆寫這些設定,但是組件層級中繼資料提供了系統管理員一個清楚的定義,以進行設定。

您是否有防止匿名存取?

請檢查程式碼是否使用 ApplicationAccessControl 屬性來指定驗證層次。接著,搜尋 AuthenticationOption 字串來尋找相關的屬性。然後,檢查是否使用最小呼叫層級的驗證,以確認呼叫每個元件都經過驗證。

[assembly: ApplicationAccessControl(
Authentication = AuthenticationOption.Call)]

您是否有使用限制的模擬層級?

您定義的服務元件模擬層級,會決定您通訊之對方遠端伺服器的模擬能力。請搜尋 ImpersonationLevel 字串來檢查程式碼設定的層級。

[assembly: ApplicationAccessControl(
ImpersonationLevel=ImpersonationLevelOption.Identify)]

請檢查是否設定遠端伺服器必需的最大限制層級。例如,如果伺服器基於驗證目的,必須識別您的身分,則可使用以上所示的識別身份層級,而不必用模擬的方式。在 Windows 2000 上,請小心地使用委派層級模擬,因為安全性內容可以不限次數地在電腦間傳遞。Windows Server 2003 則介紹了限制的委派。

注意 在 Windows Server 2003 及 Windows 2000 Service Pack 4 和更新版本中,並沒有授與所有使用者模擬的權限。

如果元件是在伺服器應用程式中,則以上所示的組件層級屬性向 Enterprise Services 登錄後,就會控制元件的起始設定。

如果元件是在程式庫應用程式中,則用戶端處理序會決定模擬層級。如果用戶端是 ASP.NET Web 應用程式,請檢查 Machine.config 檔案中 <processModel> 元素上comImpersonationLevel 的設定。

您是否有使用角色為基礎的安全性?

請藉由檢閱下列問題,來檢查程式碼是否正確地使用角色為基礎的安全性,以防止未獲授權的存取:

您是否有啟用角色為基礎的安全性?
請檢查是否已啟用角色式安全性?在預設的狀況下,Windows 2000 會停用角色式安全性。請檢查您的程式碼是否包含下列屬性:

[assembly: ApplicationAccessControl(true)]

您是否有使用元件層級存取檢查?
如果 COM+ 角色是用在介面、元件或方法層級方面,而且不僅是用來限制應用程式的存取,那麼它會是最有效的角色。請檢查您的程式碼是否包括下列屬性:

[assembly: ApplicationAccessControl(AccessChecksLevel= 
AccessChecksLevelOption.ApplicationComponent)]

此外,也請檢查各個類別是否使用 ComponentAccessControl 屬性來加註,如下所示:

[ComponentAccessControl(true)]
public class YourServicedComponent : ServicedComponent
{
}

您是否有在程式碼中執行角色檢查?
如果方法程式碼呼叫 ContextUtil.IsCallerInRole,請檢查在這些呼叫之前是否先執行 ContextUtil.IsSecurityEnabled 呼叫。如果未啟用安全性,那麼 IsCallerInRole 傳回的就永遠是 true。如果未啟用安全性,請檢查程式碼是否傳回安全性例外。

您是否有使用物件建構函式字串?

在程式碼中搜尋 ConstructionEnabled,以尋找使用物件結構字串的類別。

[ConstructionEnabled(Default="")]
public class YourServicedComponent : ServicedComponent, ISomeInterface

如果使用物件結構函式字串,請檢閱下列問題:

您是否有將敏感性資料儲存在結構函式字串中?
如果儲存的是連接字串之類的資料,請檢查資料在儲存到 COM+ 目錄之前是否已加密。程式碼應該先將資料加密,然後才可透過 Construct 方法將資料傳遞給元件。

您是否有提供預設的結構字串?
如果資料具敏感性,就不要提供預設的結構字串。

您是否有在中介層 (Middle Tier) 進行稽核?

您應該跨分散式應用程式進行稽核。請檢查服務元件是否記錄了操作和異動。透過 SecurityCallContext 物件可以使用原始呼叫者識別身份。如果已使用下列屬性,針對處理序和元件層級檢查而設定應用程式的安全性層級,那麼就可在中介層進行稽核。

[assembly: ApplicationAccessControl(AccessChecksLevel= 
AccessChecksLevelOption.ApplicationComponent)]
回到頁首回到頁首

遠端服務

這一節會指出您應該考慮的檢閱要點,來檢閱使用 .NET 遠端服務的程式碼。有關這一節所提出各項問題的詳細資訊,請參閱單元 13<建置安全的遠端元件>。

您是否有將物件當做參數傳遞?

您是否有使用自訂驗證和主體物件?

如何設定 Proxy 憑證?

您是否有將物件當做參數傳遞?

如果您使用 TcpChannel ,而且您的 API 元件接受自訂物件參數,或者如果自訂物件通過呼叫內容來傳遞,那麼您的程式碼會有兩個安全性弱點。

如果當作參數傳遞的物件源自於 System.MarshalByRefObject,那麼可藉由參考傳遞該物件。這種情況下,物件需要一個 URL 來支援呼叫回應給用戶端。用戶端 URL 也有可能會被欺騙,而導致呼叫回應給替代電腦。

如果當做參數傳遞的物件支援序列化,就能以值傳遞該物件。在這個例子中,當程式碼在伺服器上進行還原序列化時,請檢查它是否驗證每個欄位項目,以防止插入惡意的資料。

若要防止自訂物件以參數或值傳遞給遠端的元件,請將伺服器端格式子通道接收上的 TypeFilterLevel 屬性設定為 TypeFilterLevel.Low

若要尋找呼叫內容中所傳遞的物件,請搜尋 ILogicalThreadAffinative 字串。只有實作這個介面的物件可以在呼叫內容中傳遞。

您是否有使用自訂驗證和主體物件?

如果您使用自訂物件,您是否會依賴從用戶端傳來的主體物件?這是具有潛在危險的,因為惡意的程式碼會建立一個主體物件,其內含擴充式角色來提高使用權限。如果您使用這種方式,請檢查您是否只有將它與 Out-of-Band 機制 (如 IPSec 原則) 一起使用,以限制可以連接到您元件的用戶端電腦。

如何設定 Proxy 憑證?

請檢閱您的用戶端程式碼如何設定遠端 Proxy 上的憑證。如果使用明確的憑證,那麼在何處維護這些憑證?這些憑證應該加密並儲存在安全的位置,例如受限制的登錄機碼。而且不應該將它們以固定寫在程式碼中的方式放在純文字中。在理想情況下,用戶端程式碼應該使用該用戶端處理序權杖及預設憑證。

回到頁首回到頁首

資料存取程式碼

這一節會指出您應該考慮的檢閱要點,來檢閱您的資料存取程式碼。有關這一節所提出各項問題的詳細資訊,請參閱單元 14<建置安全的資料存取>。

您是否有防止 SQL 注入?

您是否有使用 Windows 驗證?

您是否有保障資料庫連接字串的安全?

如何限制未獲授權的程式碼?

如何保障資料庫中敏感資料的安全?

您是否有處理 ADO .NET 例外?

您是否有關閉資料庫連線?

您是否有防止 SQL 注入?

請檢查程式碼是否經由驗證輸入來防止 SQL 注入攻擊、使用最小權限帳戶來連接到資料庫,以及使用參數化儲存程序或參數化 SQL 命令。如需詳細資訊,請參閱本單元前面的<SQL 注入>。

您是否有使用 Windows 驗證?

藉由使用 Windows 驗證,您不需要跨網路即可將憑證傳遞到資料庫伺服器,而且連接字串不包含使用者名稱和密碼。如以下範例所示,Windows 驗證連接字串會使用 Trusted_Connection='Yes' 或 Integrated Security='SSPI'。

"server='YourServer'; database='YourDatabase' Trusted_Connection='Yes'"
"server='YourServer'; database='YourDatabase' Integrated Security='SSPI'"

您是否有保障資料庫連接字串的安全?

檢閱程式碼是否正確且安全地使用資料庫連接字串?這些字串不應該以固定寫在程式碼中的方式儲存在純文字的設定檔中,尤其是當連接字串包含使用者名稱和密碼時。

搜尋 Connection 字串來尋找 ADO .NET 連線物件,並檢閱如何設定 ConnectionString 屬性。

您是否有加密連接字串?
請檢查程式碼是否擷取加密的連接字串,然後進行解密。程式碼應該使用 DPAPI 來加密,以避免金鑰管理的問題。

您是否有使用空白密碼?
請不要使用。請檢查所有 SQL 帳戶是否具有增強式密碼。

您是否有使用 sa 帳戶或其他具有高權限的帳戶?
請不要使用 sa 帳戶或任何具有高權限的帳戶,例如 sysadmin 的成員或 db_owner 角色。這是一般常犯的錯誤。請檢查是否使用最小權限帳戶,它在資料庫中只有限制的權限。

您是否有使用保存安全性資訊 (Persist Security Info)?
請檢查 Persist Security Info 屬性是否未設定為 true yes ,因為如果這樣設定,那麼在開啟連線之後,就可從連線取得敏感資訊,包括使用者名稱和密碼。

如何限制未獲授權的程式碼?

如果您已寫好一個資料存取類別程式庫,那該如何防止未獲授權的程式碼存取您的程式庫,進而存取資料庫?一個方法就是使用 StrongNameIdentityPermission 要求來限制呼叫程式碼,使它只能呼叫以特定增強名稱私密金鑰簽章的程式碼。

如何保障資料庫中敏感資料的安全?

如果要將敏感資料 (如信用卡號碼) 儲存在資料庫中,該如何保障資料的安全?您應該檢查資料是否已經過對稱式加密演算法 (如 3DES) 進行加密。

如果使用此方法,那又該如何保障 3DES 加密金鑰的安全?您的程式碼應該使用 DPAPI 來加密 3DES 加密金鑰,然後將加密金鑰儲存在受限制的位置,如登錄。

您是否有處理 ADO .NET 例外?

請根據所使用的 ADO .NET 資料提供者,來檢查所有資料存取程式碼是否放在 try/catch 區塊之內,以及程式碼是否處理 SqlExceptionsOleDbExceptions OdbcExceptions

您是否有關閉資料庫連線?

請檢查程式碼是否不易受攻擊,即使發生例外,而資料庫連線仍保持開啟狀態。請檢查程式碼是否關閉 finally 區塊之內的所有連線,或連線物件是否在 C# using 陳述式之內建構的,如下所示。This automatically ensures that it is closed.

using ((SqlConnection conn = new SqlConnection(connString)))
{
conn.Open();
// 連線會中斷的情況包括如果產生例外,或控制流程
// 正常地離開 using 陳述式的範圍。
}
回到頁首回到頁首

總結

安全性程式碼的檢閱類似於一般程式碼的檢閱或檢查,但所著重的是如何識別可能造成安全性弱點的程式碼瑕疵。額外的好處是,解決了安全性瑕疵也會使得程式碼更加穩固。

本單元已告訴您如何檢閱 Managed 程式碼,以識別主要的安全性問題,包括 XSS、SQL 注入,以及緩衝區溢位等問題。其中也說明了如何識別其他更細微的瑕疵,這些瑕疵都可能造成安全性弱點,而導致攻擊者得逞。

安全性程式碼的檢閱並不是萬靈丹。但它還是有一定的效果,所以應該作為程式開發週期的一項常態作業。

回到頁首回到頁首

其他資源

如需詳細資訊,請參閱 MSDN 上的文章《Secure Coding Guidelines for the .NET Framework (英文)》,網址為:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/seccodeguide.asp


回到頁首回到頁首