在ASP.NET中防止注入攻擊

cnbird發表於2009-08-24

http://blog.zxbc.cn/space-365-do-blog-id-5595.html

 

目的:

·  對輸入的字串長度,範圍,格式和型別進行約束.

·  在開發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驗證控制元件來約束伺服器控制元件輸入.約束其它來源的輸入可以使用正規表示式和自定義的驗證規則.

·  拒絕.檢測已知的有害資料輸入並拒絕.

·  過濾.有時候你會希望過濾掉使用者輸入中那些有安全隱患的那些部分.例如,你的程式允許自由格式的輸入,比如備註欄位,你會允許特定的安全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控制元件輸入欄位,在伺服器端程式碼中進行驗證,使用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>

  在上面的程式碼中,正規表示式被用於限定輸入的名字為字母(允許大寫字母和小寫字母),空格,單名省略號象O`Dell和句點.此外,輸入的字元長度被限定在40個字元.

  注意正規表示式驗證控制元件(RegularExpressionValidator)會自動加入脫字元(^)和美元符號($)作為開始和結束的分隔符.如果你沒有在自定義的表示式中加入他們那麼最好加入.加入分隔符只是為了讓你的表示式得到想要的那部分資料內容.

  使用正規表示式類(Regex Class)

  如果你沒有使用伺服器端的控制元件(意味著你不能使用驗證控制元件),或者你需要其它的輸入欄位源而非表單欄位(比如查詢字串引數和cookies),那麼你可以使用正規表示式類(Regex class).

  使用正規表示式類

1.        加入使用using字首的語句匯入System.Text.RegularExpressions名稱空間.

2.        確認正規表示式包含”^”和”$”(字串開始處,字串結束處).

3.        呼叫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

}

  如果你不能把經常使用的正規表示式快取起來,你應該使用IsMatch靜態方法來改進效能防止不必要的物件建立過程.

  驗證數字欄位
將使用者登入名稱、密碼等資料加密儲存。加密使用者輸入的資料,然後再將它與資料庫中儲存的資料比較,這相當於對使用者輸入的資料進行了“消毒”處理,使用者輸入的資料不再對資料庫有任何特殊的意義,從而也就防止了攻擊者注入SQL命令。System.Web.Security.FormsAuthentication類有一個HashPasswordForStoringInConfigFile,非常適合於對輸入資料進行消毒處理。
  在大多數情況下,應該驗證數字的輸入和範圍.使用伺服器控制元件驗證數字欄位的輸入和範圍,使用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.F
orm[“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程式碼

1.        在@ Page頁面元素內加以下欄位ValidateRequest = “false”禁用ASP.NET請求驗證

2.        使用HtmlEncode方法對輸入的字串進行編碼

3.        使用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(
HttpUtility.HtmlEncode(htmlInputTxt.Text));

//
Selectively allow and <i>

sb.Replace(“<b>”, “<b>”);
sb.Replace(“</b>”, “”);
sb.Replace(“<i>”, “<i>”);
sb.Replace(“</i>”, “”);
Response.Write(sb.ToString());
}
</script>
<html>
<body>
<form id=”form1″ runat=”server”>
<div>
<asp:TextBox ID=”htmlInputTxt” Runat=”server”
TextMode=”MultiLine” Width=”318px”
Height=”168px”></asp:TextBox>
<asp:Button ID=”submitBtn” Runat=”server”
Text=”Submit” OnClick=”submitBtn_Click” />
</div>
</form>
</body>
</html>

  驗證查詢字串的值

  驗證查詢字串的長度,範圍,格式和型別.通常,你使用一個合併的正規表示式來完成以下任務:

·  約束輸入值

·  設定明確的範圍檢查條件

·  指定輸入的型別並將它轉換成ASP.NET平臺下的型別,處理任何由型別轉換引發的異常下面的程式碼示例演示了使用Regex類驗證由查詢字串傳遞過來的名字字串

void Page_Load(object sender, EventArgs e)

{
if (!System.Text.RegularExpressions.Regex.IsMatch(
Request.QueryString[“Name”], @”^[a-zA-Z`.”s]{1,40}$”))
Response.Write(“Invalid name parameter”);
else
Response.Write(“Name is ” + Request.QueryString[“Name”]);
}

  驗證Cookie值

  象查詢字串這樣被儲存在Cookie裡面的值很容易被使用者修改.同樣地驗證這些值的長度,範圍,格式和型別.

驗證檔案和URL地址

  如果你的程式允許輸入檔名,檔案地址或者檔案存放路徑,你需要驗證它們的格式是否正確並且根據你的程式實際情況它指向一個有效的位置.如果此步驗證失敗,你的程式可能會被錯誤地要求訪問檔案.

  驗證檔案路徑

  為了避免你的程式被使用者利用來訪問檔案,防止接受使用者編寫程式碼輸入的檔案或者檔案路徑.例如 :

·  如果你接受輸入檔名,使用System.IO.Path.GetFileName方法來取得檔案的全稱

·  如果你不得不接受輸入檔案路徑,使用System.IO.Path.GetFullPath來取得完整的檔案路徑
  使用MapPath方法防止跨應用程式的對映

  如果你使用MapPath方法在伺服器上對映一個提供的虛擬目錄到一個物理目錄,使用Request.MapPath方法的一個帶bool引數的過載版本來防止跨應用程式的對映.下面是此項技術的示例程式碼 :

try

{
string mappedPath = Request.MapPath( inputPath.Text,
Request.ApplicationPath, false);
}
catch (HttpException)

{

//
Cross-application mapping attempted

}

  最終的false引數將會防止跨應用程式的對映.這意味著使用者不允許使用".."這樣的語法提供一個不在你所指定的虛擬目錄裡面的非法路徑.

  如果你使用伺服器控制元件,你可以使用Control.MapPathSecure方法獲取虛擬目錄對應的實際目錄地址.

  Control.MapPathSecure方法在訪問一個非授權的檔案時丟擲一個HttpException的異常.需要更多資訊,請參看.NET Framework文件中的Control.MapPathSecure方法介紹.

  使用程式碼訪問安全機制限制檔案輸入輸出

  管理員可以通過設定程式使它的可信度為"中"來限制程式向它所在的虛擬目錄讀寫檔案的能力..NET程式碼安全機制可以保證程式在它所在的虛擬目錄之外沒有任何的檔案訪問權利.

  要設定一個應用程式的信任度為”中”,可以在Web.config或者Machine.config檔案中加入:

<trust level = “Medium” />

  驗證URL

  你可以用象下面的這樣的正規表示式來對URL進行特徵匹配.

^(?:http|https|ftp)://[a-zA-Z0-9″.”-]+(?:”:”d{1,5})?(?:[A-Za-z0-9″.”;”:”@”&”=”+”$”,”?/]|%u[0-9A-Fa-f]{4}|%[0-9A-Fa-f]{2})*$

  這只是約束輸入的格式,不驗證它是否在應用程式可接受的範圍內.你應該驗證它是否在你的程式的上下文中有效.例如,您的應用程式是否跟你指定的伺服器進行通訊?

  第三步.對不安全程式碼進行編碼

  如果您輸入文字輸入到一個網頁,使用HttpUtility.HtmlEncode方法對它進行編碼.如果這些文來自於使用者輸入,資料庫或者一個本地檔案,請確保總是這樣做.

  同樣地,如果您書寫的URL裡面包含不安全的字元因為他們來自於使用者輸入內容,資料庫等,使用HttpUtility.UrlEncode方法進行編碼.

  為了防止儲存資料前編碼可能會使儲存的資料受到破壞,請確保在將它們顯示出來時儘可能後面的步驟將它們編碼.

  使用HtmlEncode對不安全的輸出編碼

  HtmlEncode對HTML標記置換成特殊含文的字串來表示這些符號而又讓瀏覽器不把它們當作HTML標記來解釋處理.比如.”<”被置換成<” (冒號) 被替換成" 這些標記被顯示成無害的文字.

<%@ Page Language=”C#” ValidateRequest=”false” %>

<script runat=”server”>
void submitBtn_Click(object sender, EventArgs e)
{
Response.Write(HttpUtility.HtmlEncode(inputTxt.Text));
}
</script>

<html xmlns=”http://www.w3.org/1999/xhtml
” >
<body>
<form id=”form1″ runat=”server”>
<div>
<asp:TextBox ID=”inputTxt” Runat=”server”
TextMode=”MultiLine” Width=”382px” Height=”152px”>
</asp:TextBox>
<asp:Button ID=”submitBtn” Runat=”server” Text=”Submit”
OnClick=”submitBtn_Click” />
</div>
</form>
</body>
</html>

  檢視HTML編碼的效果,請建立一個虛擬目錄將前述的檔案放進去,執行此頁面,在文字框中輸入一些HTML程式碼,點選提交按鈕.例如,下面的輸入被當作普通文字來顯示.

Run script and say hello <script>alert(`hello`);</script>

  如果你移除呼叫HtmlEncode方法,簡單地輸入文字的內容,瀏覽器會執行程式碼並彈出一個提示框.

  使用UrlEncode 方法對不安全的URL地址進行編碼

  如果你需要獲取有使用者輸入部分的URL引數,這可能帶來一定的安全風險,使用HttpUtility.UrlEncode方法對這個地址字串編碼.

HttpUtility.UrlEncode(urlString);

  第四步.對SQL語句使用命令引數方式.

  為了避免注入式攻擊請使用SQL的引數方式.引數(Parameters)集合提供型別檢測和長度檢測.如果你使用引數集合,輸入的內容將被當作文字值來對待,資料庫不會執行包含在其中的程式碼.使用引數集方式的一個額外的好處是,你可以嚴格限定輸入的型別和長度.如果輸入型超出範圍將會觸發異常.

  當呼叫一個儲存過程時使用引數集

  下面的程式碼片段演示了在呼叫儲存過程時使用引數集的例子.

SqlDataAdapter myCommand = new SqlDataAdapter(“AuthorLogin”,
myConnection);
myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
SqlParameter parm = myCommand.SelectCommand.Parameters.Add(
“@LoginId”, SqlDbType.VarChar, 11);
parm.Value = Login.Text;

  在建立你自己的SQL語句時使用引數集.

  如果你不能使用儲存過程,你仍然可以使用引數集,請看下面的程式碼.

SqlDataAdapter myCommand = new SqlDataAdapter(
“SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id”, myConnection);
SQLParameter parm = myCommand.SelectCommand.Parameters.Add(
“@au_id” ,SqlDbType.VarChar, 11);
Parm.Value = Login.Text;

  第五步.驗證ASP.NET的錯誤資訊沒有被返回到客戶端

  你可以使用<customErrors>元素來配置客戶端,一般的錯誤資訊應該被程式錯誤檢測機制返回到客戶端.

  請確認已經更改web.config中的mode屬性為”remoteOnly”,下面是示例.

<customErrors mode = “remoteOnly”>

  安在裝了一個ASP.NET 的程式之後,你可以按照如下設定指定客戶端的錯誤資訊頁面。

<customErrors mode = “on” defaultRedirect = “YourErrorPage.htm”>


相關文章