程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> .NET實例教程 >> 在ASP.NET中防止注入攻擊[翻譯]

在ASP.NET中防止注入攻擊[翻譯]

編輯:.NET實例教程

出處: MSDN
翻譯:雲中城 BLOG

應用范圍:


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驗證控件來約束服務器控件輸入.約束其它來源的輸入可以使用正則表達式和自定義的驗證規則. 
拒絕.檢測已知的有害數據輸入並拒絕. 
過濾.有時候你會希望過濾掉用戶輸入中那些有安全隱患的那些部分.例如,你的程序允許自由格式的輸入,比如備注字段,你會允許特定的安全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&g
t;
 <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).

使用正則表達式類

加入使用using前綴的語句導入System.Text.RegularExpressions命名空間. 
確認正則表達式包含"^"和"$"(字串開始處,字串結束處). 
調用Regex類的IsMat
ch方法,下面是代碼示例. 
 

// 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靜態方法來改進性能防止不必要的對象創建過程.

驗證數字字段

在大多數情況下,應該驗證數字的輸入和范圍.使用服務器控件驗證數字字段的輸入和范圍,使用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 betwe
en 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(
                            HttpUtility.HtmlEncode(HtmlInputTxt.Text));
    // Selectively allow  and <i>
    sb.Replace("&lt;b&gt;", "<b>");
    sb.Replace("&lt;/b&gt;", "");
    sb.Replace("&lt;i&gt;", "<i>");
    sb.Replace("&lt;/i&gt;", "");
    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標記來解釋處理.比如."<"被置換成&lt; " (冒號) 被替換成&quot; 這些標記被顯示成無害的文本.

 

<%@ 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;

 

如果需要獲取更多的如果防止SQL注入攻擊的信息請參看How to : Protect From SQL Injection in ASP.Net

第五步.驗證ASP.Net的錯誤信息沒有被返回到客戶端

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

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

<customErrors mode = "remoteOnly">

安在裝了一個ASP.Net 的程序之後,你可以按照如下設定指定客戶端的錯誤信息頁面。

<customErrors mode = "on" defaultRedirect = "YourErrorPage.htm">

額外的資源,請查看相關主題內容 :

在ASP.Net中如何使用正則表達式約束輸入.

防止SQL注入攻擊

防止跨站腳本攻擊

 

PS: 終於翻完了啊,真累,花了將近三天的時間,其實看看這些句子都很簡單的。我第一次做翻譯,譯得不好的話請大家多多包涵,謝謝。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved