在ASP.NET中防止注入攻擊[翻譯]
出處: MSDN 原文地址
應用範圍:
ASP.NET vertion 1.1
ASP.NET vertion 2.0
概要:
文本主要介紹如何校正使用者輸入從而防止注入式攻擊.校正使用者輸入是非常必要的,幾乎所有程式級的攻擊都包含惡意輸入的手段.
你應該校正包括欄位,查詢字串參數,Cookie等一切使用者輸入項來保護你的程式免受注入攻擊.你得假設所有的使用者輸入都是惡意的,確保在伺服器端對所有的使用者輸入進行校正.使用基於用戶端的驗證可以減少頁面的住返次數,改進效能,改善使用者體驗,但是不要僅僅依賴於此,因為用戶端的驗證很容易就可以被駭客騙過去.
為了驗證輸入內容,你應該為每一個輸入欄位定義可接受的輸入規則.比較好的作法是從輸入欄位的長度,範圍,格式,類型來作約束.使用可接受的字元約束列表而不是非法字元列表來約束輸入.使用非法字元列資料表條件約束的方式不好,是因為你幾乎不可能過濾所有的有害輸入.
如果你需要接受HTML字元輸入,最好使用HtmlEncode之類的方法將它進行編碼確保安全再將它們顯示出來.
內容:
目的
總括
分步實施提要
第一步. 使用ASP.NET 請求校正.
第二步. 使用權用約束輸入.
第三步. 對不安全的輸入進行編碼.
第四步. 對Sql語句使用命令參數方式.
第五步. 驗證ASP.NET的錯誤沒有被返回用戶端.
額外的資源
目的:
對輸入的字串長度,範圍,格式和類型進行約束.
在開發ASP.NET程式時使用請求驗證防止注入攻擊.
使用ASP.NET驗證控制項進行輸入驗證.
對不安全的輸出編碼.
使用命令參數集模式防止注入攻擊.
防止錯誤的詳細資料被返回到用戶端.概述 :
你應該在程式中驗證所有的不信任輸入.你應該假定所有的使用者輸入都是非法的.使用者可以在應用程式中提供表單欄位,查詢字串,用戶端cookies和瀏覽器環境值比如使用者代理程式字串和IP地址等.
弱輸入校正通常為注入攻擊提供了機會.下面是常見的利用弱輸入校正或無輸入校正進行攻擊的手段.
SQL 注入(SQL injection). 如果你使用使用者的輸入值來動態構造SQL語句,那麼資料庫可能執行攻擊性的有害SQL語句.
跨站指令碼(Cross-site scripting). 跨站指令碼攻擊利用網頁驗證漏洞注入用戶端指令碼.接下來這些代碼被發送到受信任的用戶端電腦上並被瀏覽器解釋執行.因為這些代碼來自受信任的網站,所以瀏覽器無法得知這些代碼是有害的.
未授權的檔案訪問(Unauthorized file access).如果你的代碼從調用者那裡接受輸入,惡意使用者可以看到你對檔案的操作過程從而訪問那些受保護的檔案或者使用你的代碼注入非法資料.
注意 : 注入攻擊可通過使用HTTP或HTTPS Secure Socket Layer(SSL) 串連. 傳輸加密技術不能用來防禦攻擊.
通常的輸入驗證方法總結如下.你應在所有的需要通過網路輸入的地方進行驗證,比如文字框和其它表單輸入欄位, 查詢字串參數,cookies,伺服器端變數和網路方法參數.注意,過濾策略應該是只允許正確的輸入然後拒絕非法輸入.這是因為定義正確的輸入策略比過濾所有的非法輸入要容易,那通常很難包括所有的非法輸入.
通入如下幾個方面驗證輸入內容:
約束.驗證是否輸入的是正確的類型,字元長度,格式和範圍.可以應用ASP.NET驗證控制項來條件約束服務器控制項輸入.約束其它來源的輸入可以使用Regex和自訂的驗證規則.
拒絕.檢測已知的有害資料輸入並拒絕.
過濾.有時候你會希望過濾掉使用者輸入中那些有安全隱患的那些部分.例如,你的程式允許自由格式的輸入,比如備忘欄位,你會允許特定的安全HTML標記象<b>,<i>及其它的HTML標記.
步驟提要
通過以下步驟保護你的ASP.NET程式不受注入式攻擊危害 :
第一步.使用ASP.NET請求驗證.
第二步.約束輸入.
第三步.對不安全的輸出進行編碼.
第四步.對SQL查詢語句使用命令參數.
第五步.驗證ASP.NET的出錯資訊沒有泄漏至用戶端.
下面的章節將對這些步驟進行詳細討論.
第一步.使用ASP.NET請求驗證.
預設地,ASP.NET 1.1和2.0請求驗證會對送至伺服器的資料檢測是否含有HTML標記元素和保留字元.這可以防止使用者向程式中輸入指令碼.請求驗證會對照一個有潛在威脅的字串列表進行匹配,如果發現異常它會拋出一個HttpRequestValidationException類型的異常.
你可以在你的web.config檔案中的<pages>元素中加入validateRequest="false" 或在單獨的頁面的@Pages元素裡面設定ValidateRequest = "false"來禁用此項功能.
如果你想禁用請求驗證功能,你可以僅在需要的頁面禁用它.比如你在程式頁面上包含一個可接受HTML格式輸入的欄位.
確定在Machine.config檔案中請求驗證功能被開啟.
請求驗證功能在ASP.NET中被預設啟用.你可以在Machine.config.comments檔案中看到如下的預設設定.
<pages validateRequest = "true" ... />
確認你沒有修改你的伺服器的Machine.config和應用程式的Web.config檔案裡的預設設定.
測試ASP.NET請求驗證
你可以測試請求驗證的作用.建立一個ASP.NET頁面通過設定ValidateRequest = "fasle"禁用請求驗證,代碼如下 :
<%@ Language="C#" ValidateRequest="false" %>
<html>
<script runat="server">
void btnSubmit_Click(Object sender, EventArgs e)
{
// If ValidateRequest is false, then 'hello' is displayed
// If ValidateRequest is true, then ASP.NET returns an exception
Response.Write(txtString.Text);
}
</script>
<body>
<form id="form1" runat="server">
<asp:TextBox id="txtString" runat="server"
Text="<script>alert('hello');</script>" />
<asp:Button id="btnSubmit" runat="server" OnClick="btnSubmit_Click"
Text="Submit" />
</form>
</body>
</html>
當你運行頁面的時候,"Hello"被顯示在一個訊息框中,因為在txtString中的指令碼被執行並被用戶端的瀏覽器處理.
如果你設定ValidateRequest = "true" 或者移除ValidateRequest頁面屬性,ASP.NET請求驗證會拒絕指令碼輸入並拋出一個象下面這樣的錯誤資訊.
A potentially dangerous Request.Form value was detected from the client (txtString="<script>alert('hello").
注意 不要僅僅依賴請求驗證功能,而只是把它作為自定驗證的輔導手段.
第二步.約束輸入
要約束輸入通過如下方法 :
使用伺服器端的輸入驗證.不要依賴於用戶端的驗證,因為它很容易就被繞過.使用用戶端驗證是為了減少頁面返住次數提升效能,改進使用者體驗.
驗證輸入的長度,範圍,格式和類型.確保輸入內容是符合要求的正確內容.
使用強資料類型.為數字類型的輸入指定如Integer或者Double的類型.為字元輸入指定為String資料類型.為日期時間輸入指定DateTime類型.要驗證表單裡面的HTML控制項輸入欄位,在伺服器端代碼中進行驗證,使用RegexRegex類型可以協助約束字元輸入.下面的章節介紹如何約束普通輸入類型的變數.
驗證字串欄位
要驗證字串欄位,如姓名,地址,傳真,生份證號碼,使用Regex.
約束可接受的字元範圍.
啟動格式規則.例如,基於模式的欄位如稅號,郵編,郵遞區號需要規定的字元模式.
驗證長度.
使用Regex驗證控制項(RegularExpresionValidator)
要使用則運算式驗證控制項需要設定待驗證的控制項名(ControlToValidate),驗證運算式(ValidationExpression)和出錯提示(ErrorMessage).相關的屬性設定請看下面的程式碼範例.
<form id="WebForm" method="post" runat="server">
<asp:TextBox id="txtName" runat="server"></asp:TextBox>
<asp:RegularExpressionValidator id="nameRegex" runat="server"
ControlToValidate="txtName"
ValidationExpression="^[a-zA-Z'.\s]{1,40}$"
ErrorMessage="Invalid name">
</asp:regularexpressionvalidator>
</form>
在上面的代碼中,Regex被用於限定輸入的名字為字母(允許大寫字母和小寫字母),空格,單名省略符號象O'Dell和句點.此外,輸入的字元長度被限定在40個字元.
注意 Regex驗證控制項(RegularExpressionValidator)會自動加入脫字元(^)和貨幣符號($)作為開始和結束的分隔字元.如果你沒有在自訂的運算式中加入他們那麼最好加入.加入分隔字元只是為了讓你的運算式得到想要的那部分資料內容.
使用Regex類(Regex Class)
如果你沒有使用伺服器端的控制項(意味著你不能使用驗證控制項),或者你需要其它的輸入欄位源而非表單欄位(比如查詢字串參數和cookies),那麼你可以使用Regex類(Regex class).
使用Regex類
- 加入使用using首碼的語句匯入System.Text.RegularExpressions命名空間.
- 確認Regex包含"^"和"$"(字串開始處,字串結束處).
- 調用Regex類的IsMatch方法,下面是程式碼範例.
// Instance method:
Regex reg = new Regex(@"^[a-zA-Z'.\s]{1,40}$");
Response.Write(reg.IsMatch(txtName.Text));
// Static method:
if (!Regex.IsMatch(txtName.Text,@"^[a-zA-Z'.\s]{1,40}$"))
{
// Name does not match expression
}
如果你不能把經常使用的Regex緩衝起來,你應該使用IsMatch靜態方法來改進效能防止不必要的對象建立過程.
驗證數字欄位
在大多數情況下,應該驗證數位輸入和範圍.使用伺服器控制項驗證數字欄位的輸入和範圍,使用RangeValidator控制項.RangeValidator支援貨幣,日期,整型,雙精確度和字串類型的資料.
使用RangeValidator控制項需要設定需要驗證的控制項名(ControlToValidate),類型(Type),最小值(MinimumValue),最大值(MaximumValue),和出錯提示資訊(ErrorMessage)屬性.下面是程式碼範例 :
<asp:RangeValidator
ID="RangeValidator1"
Runat="server"
ErrorMessage="Invalid range. Number must be between 0 and 255."
ControlToValidate="rangeInput"
MaximumValue="255"
MinimumValue="0" Type="Integer" />
如果你沒使用伺服器控制項,你可以將輸入值轉化成整型再進行驗證來完成對數位範圍驗證.例如,要驗證一個整數是否合法,使用ASP.NET2.0提供的新方法Int32.TryParse將輸入值轉化為System.Int32的變數類型.這個方法會在轉換失敗時返回false.
Int32 i;
if (Int32.TryParse(txtInput.Text, out i) == false)
{
// Conversion failed
}
如果你使用早先的ASP.NET版本,可以在try/catch語句塊中 使用Int32.Parse或者Convert.ToInt32方法並可以在轉換失敗時處理拋出的FormatException錯誤.
下面的範例程式碼示範了如何驗證來自HTML文字框的整數類型的類型和範圍.
<%@ Page Language="C#" %>
<script runat="server">
void Page_Load(object sender, EventArgs e)
{
if (Request.RequestType == "POST")
{
int i;
if (Int32.TryParse(Request.Form["integerTxt"], out i) == true)
{
// TryParse returns true if the conversion succeeds
if ((0 <= i && i <= 255) == true)
{
Response.Write("Input data is valid.");
}
else
Response.Write("Input data is out of range");
}
else
Response.Write("Input data is not an integer");
}
}
</script>
<html>
<body>
<form id="form1" action="NumericInput.aspx" method="post">
<div>
Enter an integer between 0 and 255:
<input name="integerTxt" type="text" />
<input name="Submit" type="submit" value="submit" />
</div>
</form>
</body>
</html>
驗證日期欄位
你需要驗證日期欄位是否是正確的類型.在大多數情況下,你也需要驗證它們的範圍,如驗證它們是否是將來或是過去的時間.如果你使用伺服器控制項來捕獲一個日期輸入值,同時你希望這個值在一個特定的範圍內,你可以使用範圍驗證控制項(RangeValidator)並設定它允許的類型為Date類型.這個控制項允許你指定一個特殊的時間段通過設定起始的時刻.如果你需要以今天的時間作為參照來驗證,比如驗證一個時間是在將來還是過去,你可以使用CustomValidator驗證控制項.
使用CustomValidator控制項來驗證一個日期需要設定ControlToValidate和ErrorMessage屬性,在OnServerValidate事件中指定一個自訂的驗證邏輯方法.下面是範例程式碼.
<%@ Page Language="C#" %>
<script runat="server">
void ValidateDateInFuture(object source, ServerValidateEventArgs args)
{
DateTime dt;
// Check for valid date and that the date is in the future
if ((DateTime.TryParse(args.Value, out dt) == false) ||
(dt <= DateTime.Today))
{
args.IsValid = false;
}
}
</script>
<html>
<body>
<form id="form1" runat="server">
<div>
<asp:Label ID="Label1" Runat="server"
Text="Future Date:"></asp:Label>
<asp:TextBox ID="futureDatetxt" Runat="server"></asp:TextBox>
<asp:CustomValidator
ID="CustomValidator1" Runat="server"
ErrorMessage="Invalid date. Enter a date in the future."
ControlToValidate="futureDatetxt"
OnServerValidate="ValidateDateInFuture">
</asp:CustomValidator>
<br />
<asp:Button ID="submitBtn" Runat="server" Text="Submit" />
</div>
</form>
</body>
</html>
注意 上面的代碼使用的方法DateTime.TryParse是ASP.NET2.0提供的新方法.
過濾自由文字欄位
過濾輸入,你需要使不安全的輸入不被當作代碼來對待.例如,你的程式使使用者不能讀取共用資料庫內的資料,你首先需要過濾資料使它們在輸出的時候沒有危險.使用HttpUtility.HtmlEncode方法先對輸入值進行編碼.
允許有限的輸入HTML代碼
- 在@ Page頁面元素內加以下欄位ValidateRequest = "false"禁用ASP.NET請求驗證
- 使用HtmlEncode方法對輸入的字串進行編碼
- 使用StringBuilder對象,調用它的Replace方法對字元中的HTML進行替換
下面的代碼給出了這種辦法的樣本.此版面設定ValidateRequest = "fasle"禁用了ASP.NET請求驗證.它的HTML編碼為了顯示簡單的文字格式設定允許使用<b>和<i>標記.
<%@ Page Language="C#" ValidateRequest="false"%>
<script runat="server">
void submitBtn_Click(object sender, EventArgs e)
{
// Encode the string input
StringBuilder sb = new StringBuilder(