目的:

对输入的字串长度,范围,格式和类型进行约束.
在开发 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).

  使用正则表达式类

  加入使用 using 前缀的语句导入 System.Text.RegularExpressions 命名空间. 确认正则表达式包含"^"和"$"(字串开始处,字串结束处).
调用 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 静态方法来改进性能防止不必要的对象创建过程.

  验证数字字段

  在大多数情况下,应该验证数字的输入和范围.使用服务器控件验证数字字段的输入和范围,使用 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 代码


  在@ 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("
<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;

  如果需要获取更多的如果防止 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">