转:代码安全审查

时间:2024-02-23 10:54:30

本模块内容

安全代码审查的目的是要识别出会导致安全问题和事故的不安全编码技术和漏洞。虽然可能很耗时,但代码审查必须是项目开发周期中的常规事件,这是因为在开发时修复安全缺陷会比以后在产品部署或维护修复周期中再做这项工作节省大量的成本和工作量。

本模块帮助您审查使用 Microsoft .NET Framework 建立的托管 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

如何使用本模块

使用本模块可以创建或扩展现有代码审查过程。必须确保代码审查是您的开发周期的不可或缺的一部分,并且要知道代码审查的效果与所投入的资源和预算的数量是成正比的。

由于在开发时修复安全缺陷所需的成本和工作量远比以后在产品部署周期中修复所需的成本和工作量小,所以,审查目标是在部署代码之前尽可能多地识别潜在的安全漏洞。

为了更好地理解本模块,请:

使用本指南的部分 III 中的其他安全模块。 参考下列这些模块,以便提供有关本模块中分级显示的审查问题的其他信息。

模块 6 .NET 安全概述

模块 7 构建安全的程序集

模块 8 代码访问安全的实践

模块 9 ASP.NET 代码访问安全性

模块 10 构建安全的 ASP.NET 页面和控件

模块 11 构建安全服务型组件

模块 12 构建安全的 Web Services

模块 13 构建安全的远程组件

模块 14 构建安全的数据访问

使用其他检查表:

检查表:体系结构和设计审查

检查表:保护 ASP.NET 的安全

检查表:保护 Web Services的安全

检查表:保护企业服务

检查表:保护远程处理

检查表:保护数据访问

FxCop

启动审查过程的好方法是通过 FxCop 分析工具运行已编译的程序集。此工具分析二进制程序集(而非源代码),以确保它们符合 MSDN 中可用的 .NET Framework 设计指南。它还检查您的程序集是否具有强大的名称,以提供防篡改和其他安全收益。此工具随规则的预定义集合提供,虽然您可以自定义和扩展它们。

有关详细信息,请参阅下列资源:

要下载 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 框架设计指南,请参阅 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconnetframeworkdesignguidelines.asp(英文)。

执行文本搜索

要帮助执行审查过程,请检查您是否熟悉可用来找到文件中的字符串的文本搜索工具。此种工具使您可以快速找到易受攻击的代码。本模块后面提供的许多审查问题都指出当查找特定漏洞时要搜索的最佳字符串。

您可能已有喜欢的搜索工具。如果没有,可以使用 Visual Studio .NET 中的“在文件中查找”工具或随 Microsoft Windows 操作系统提供的 Findstr 命令行工具。

注意 如果使用 Windows 资源管理器中的 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 流中使用输入参数时,它易受到跨站点脚本(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 属性(例如 srclowsrcstylehref)与上述标记一起使用会导致 XSS。

例如,<img> 标记的 src 属性可能是注入源,如下例所示。

<IMG SRC="javascript:alert(\'hello\');">
<IMG SRC="java
script:alert(\'hello\');">
<IMG SRC="java
script:alert(\'hello\');">

按如下所示更改 MIME 类型后,<style> 标记也可能成为注入源。

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

检查并确定您的代码是否通过筛选掉某些已知的危险字符来净化输入。不要依赖此方法,这是因为恶意用户通常会找到另一表示来绕过您的验证。相反,代码应该对已知安全的输入进行验证。下表显示了表示一些常用字符的各种方法:

表 21.2:字符表示

字符 十进制 十六进制 HTML 字符集 Unicode

"(双引号)

 

            "

 

"

 

 &quot;

 

\u0022

\'(单引号)

 

\'

 

\'

 

&apos;

 

\u0027

&(表示 and 的符号)

 

&

 

&

 

&

 

\u0026

<(小于)

 

<
            

 

<

 

<

 

\u003c

>(大于)

 

>

 

>

 

>

 

\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> 元素所配置的 requestEncodingresponseEncoding 属性进行了设置。

<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 属性将用户的“受限站点 Internet Explorer”安全区域设置应用于单独的 frame iframe。有关详细信息,请参阅模块 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/CC 网站上的“CERT Advisory CA–2000–02, Malicious HTML Tags Embedded in Client Web Requests”,网址为 http://www.cert.org/advisories/CA-2000-02.html(英文)

CERT/CC 网站上的“Understanding Malicious Content Mitigation for Web Developers”,网址为 http://www.cert.org/tech_tips/malicious_code_mitigation.html(英文)

SQL 注入

当代码使用输入参数构建 SQL 语句时,它易受到 SQL 注入攻击。与 XSS 问题一样,导致 SQL 注入攻击的原因是对用户输入给予太多信任,以及没有验证输入及其格式是否正确。

下面的过程帮助您找到 SQL 注入漏洞:

1.

查找访问数据库的代码
扫描字符串“SqlCommand”、“OleDbCommand”或“OdbcCommand”。

2.

检查代码是否使用参数化存储过程
存储过程不能单独防止 SQL 注入攻击。检查代码是否使用参数化存储过程。检查代码是否使用键入的参数对象,例如 SqlParameterOleDbParameterOdbcParameter。下面的示例显示了 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 层调用非托管代码的代码上。由于当访问数组时自动检查数组界限,托管代码本身受缓冲区溢出危害要小得多。只要调用 Win32 DLL 或 COM 对象,就应该严格检查 API 调用。

下面的过程帮助您找到缓冲区溢出漏洞:

1.

查找对非托管代码的调用
扫描您的源文件,以查找“System.Runtime.InteropServices”,它是您调用非托管代码时使用的名称空间名。

2.

检查传递给非托管 API 的字符串参数
这些参数是缓冲区溢出的主要来源。检查代码是否检查任何输入字符串的长度以验证它不超过 API 所定义的限制。如果非托管 API 接受字符指针,您可能不知道最大可允许字符串长度,除非您具有非托管源的访问权。下面的代码片段显示了常见漏洞:

void SomeFunction( char *pszInput )
            {
            char szBuffer[10];
            // 注意,没有长度检查。输入直接复制到缓冲区
            // 应该检查长度或使用 strncpy。
            strcpy(szBuffer, pszInput);
            . . .
            }
            

注意 如果使用 strncpy,缓冲区溢出仍会发生,这是因为它不检查目标字符串中的多余空格,它只限制复制的字符串的数量。

如果由于不拥有非托管代码而不能检查它,则可以通过故意传递长输入字符串和无效参数来严格测试 API。

3.

检查文件路径长度。
如果托管 API 接受文件名和路径,请检查您的包装方法是否检查文件名和路径不超过 260 个字符。这是由 Win32 MAX_PATH 常量定义的。另请注意,目录名称和注册表项最多可以为 248 个字符。

4.

检查输出字符串。
检查代码是否使用 StringBuilder 接收从非托管 API 传回的字符串。检查 StringBuilder 的长度是否足以保留非托管 API 可以退还的最长字符串,这是因为从非托管代码传回的字符串可能为任意长度。

5.

检查数组界限。
如果使用数组将输入传递给非托管 API,请检查托管包装是否验证未超出数组容量。

6.

检查非托管代码是否是使用 /GS 开关编译的。
如果您拥有非托管代码,请使用 /GS 开关启用堆栈探查,以检测某些类型的缓冲区溢出。

托管代码

使用本部分中的审查问题对整个托管源代码库进行分析。无论程序集的类型如何,这些审查问题都适用。本部分帮助您识别常见托管代码漏洞。有关本部分中产生的问题以及说明漏洞的代码示例的详细信息,请参阅模块 7 构建安全的程序集。

如果托管代码使用明确代码访问安全功能,请参阅本模块后面的代码访问安全,以获取其他审查点。下列审查问题可以帮助您识别托管代码漏洞:

您的类设计安全吗?

您是否创建了线程?

您是否使用了序列化?

您是否使用了反射?

您是否处理了例外?

您是否使用了加密?

您是否存储了机密?

您是否使用了委托?

您的类设计安全吗?

程序集只与它包含的类和其他类型一样安全。下列问题可以帮助您审查类设计的安全性:

您是否限制了类型和成员可见性?
审查任何标记为“公用”的类型或成员,并检查它是否是程序集的公用接口的一个想要的部分。

非基本类是否已封装?
如果不希望类派生,请使用已封装的关键字来避免潜在恶意子类误用代码。

对于公用基本类,可以使用代码访问安全继承需求来限制可以从类继承的代码。这是一个好的深度防御方法。

您是否使用了属性来公开字段?
检查您的类没有直接公开字段。使用属性来公开非专用字段。这使您可以验证输入值和应用其他安全检查。

您是否使用了只读属性?
验证您已经有效使用了只读属性。如果字段未设计为设置,请通过只提供 get 访问器来实现只读属性。

您是否使用了虚拟内部方法?
可以从具有类的访问权的其他程序集改写这些方法。使用声明检查或删除虚拟关键字(如果不需要它)。

您是否实现 IDisposable?
如果是,请检查当您以对象实例结束时调用了 Dispose 方法,以确保释放所有资源。

您是否创建了线程?

多线程代码容易产生敏感的、与计时相关的缺陷或赛跑条件,它们会导致安全漏洞。要找到多线程代码,请搜索文本“线程”的源代码,以识别在何处创建新线程对象,如下面的代码片段所示:

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

下列审查问题可以帮助您识别潜在线程漏洞:

您的代码是否缓存了安全检查的结果?
如果您的代码缓存安全检查的结果(例如在静态或全局变量中),然后使用标志进行后续安全决策,则特别易受攻击。

您的代码是否进行了模拟?
创建新线程的线程当前是否正在模拟?新线程始终假设进程级别的安全上下文,而非现有线程的安全上下文。

您的代码是否包含静态类构造函数?
检查静态类构造函数,以检查当两个或更多线程同时访问它们时它们是否不易受攻击。如有必要,将线程同步,以避免此情况。

您是否同步了 Dispose 方法?
如果未同步某一对象的 Dispose 方法,则很可能两个线程在同一个对象上执行 Dispose。这会产生安全问题,尤其是当清理代码释放非托管资源处理程序(例如文件、进程或线程句柄)时。

您是否使用了序列化?

支持序列化的类标记了 SerializableAttribute,或从 ISerializable 派生。要找到支持序列化的类,请对“可序列化”字符串执行文本搜索。然后审查代码中是否有下列问题:

类是否包含敏感数据?
如果是,请检查代码是否通过此方法防止敏感数据被序列化:使用或通过实现 ISerializable 对具有 [NonSerialized] 属性的数据进行标记,然后控制序列化哪些字段。

如果类需要将敏感数据序列化,请检查保护数据的方法。首先考虑对数据加密。

类是否实现了 ISerializable?
如果是,类是否只支持完全信任调用方(例如由于它安装在不包括 AllowPartiallyTrustedCallersAttribute 的强大命名程序集内)?如果类支持部分信任调用方,请检查 GetObjectData 方法实现是否使用适当的权限需求对调用代码进行了授权。好的技术是使用 StrongNameIdentityPermission 请求来限制哪些程序集可以序列化对象。

类是否验证了数据流?
如果代码包括接收序列化数据流的方法,请检查在从数据流读取每个字段时是否对每个字段进行了验证。

您是否使用了反射?

要帮助找到使用反射的代码,请搜索“System.Reflection”— 这是包含反射类型的名称空间。如果确实使用了反射,请审查下列问题,以帮助识别潜在漏洞:

您是否动态加载了程序集?
如果代码加载程序集以创建对象实例和调用类型,它是否包含输入数据中的程序集或类型名称?如果是,请检查代码是否使用权限请求进行了保护,以确保对所有调用代码进行授权。例如,使用 StrongNameIdentity 权限请求或请求完全信任。

您是否在运行时动态创建了代码?
如果程序集动态生成代码以便为调用方执行操作,请检查调用方是否不会影响生成的代码。例如,代码生成是否依赖调用方提供的输入参数?这应该是可以避免的,或者如果一定需要,请确保输入经过验证且它不会用来反面影响代码生成。

您是否在其他类型上使用了反射?
如果是,请检查只有受信任的代码可以调用您。使用代码访问安全权限要求来授权调用代码。

您是否处理了例外?

强大的代码需要安全例外处理,以确保记录了足够的例外详细信息,以便帮助进行问题诊断和帮助防止内部系统详细信息暴露给客户端。审查下列问题,以帮助识别潜在例外处理漏洞:

您以前是否曾失败?
检查代码以前是否曾失败,以避免消耗资源的不必要处理。如果代码确实失败过,请检查导致的错误是否不允许用户绕过安全检查来运行特权代码。

您如何处理例外?
避免将系统或应用程序详细信息暴露给调用方。例如,不要将调用堆栈返回给最终用户。对可以生成带有 try/catch 块的例外的资源访问或操作进行包装。只处理您知道如何处理的例外,避免使用一般包装来包装特定例外。

您是否记录了例外详细信息?
检查是否在例外源处记录了例外详细信息以帮助进行问题诊断。

您是否使用了例外筛选器?
如果是,一定注意调用堆栈中较高位置的筛选器中的代码可以在最后块中的代码之前运行。检查您不依赖最后块中的状态更改,这是因为在例外筛选器执行之前,状态更改不会发生。

有关例外筛选器漏洞的示例,请参阅模块 7 构建安全的程序集中的“例外管理”。

您是否使用了加密?

如果是,请检查您的代码不执行其自己的加密例程。相反,代码应该使用 System.Security.Cryptography 名称空间或使用 Win32 加密(例如数据保护应用程序编程接口 (DPAPI))。审查下列问题,以帮助识别潜在潜在加密相关的漏洞:

您是否使用了对称加密?
如果是,请检查当加密数据需要保留很长时间时使用了 Rijndael(现在称为高级加密标准 [AES])或三重数据加密标准 (3DES)。使用较弱(但较快)的 RC2 和 DES 算法只对具有短寿命的数据(例如会话数据)进行加密。

您是否使用了最大可能大小的密钥?
请为您正在使用的算法使用最大可能大小的密钥。较大密钥大小使针对密钥的攻击变得更困难,但降低了性能。

您是否使用了哈希?
如果是,请检查当需要规则来证明它知道与您共享的机密时使用了 MD5 和 SHA1。例如,质询响应身份验证系统使用哈希来验证客户端知道密码,而无需客户端将密码传递给服务器。将 HMACSHA1 与消息验证代码 (MAC) 一起使用,需要您和客户端共享密钥。这可以提供完整性检查和某种程度的身份验证。

您是否出于加密目的生成随机编号?
如果是,请检查代码使用 System.Security.Cryptography.RNGCryptoServiceProvider 类(而不是使用 Random 类)生成了随机编号。Random 类不生成不可重复或不可预测的真正随机编号。

您是否存储了机密?

如果程序集存储了机密,请检查设计,以检查绝对需要存储机密。如果必须存储机密,请审查下列问题,以便尽可能安全地进行此操作:

您是否在内存中存储了机密?
不要在内存中将机密以纯文本形式保存太长时间。从存储区检索机密,将它解密,使用它,然后在存储此机密的位置中替换零。

您是否在 Web.config 或 Machine.config 中存储纯文本密码或 SQL 连接字符串?
请不要这样做。请使用 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, Building Secure ASP.NET Web Applications: Authentication, Authorization, and Secure Communication”的“How To”部分中的“How To: Create a DPAPI Library”,网址为 http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/SecNetHT07.asp(英文)。

您是否在注册表中了存储机密?
如果是,请检查它们是否首先加密然后使用受限 ACL 来确保安全(如果它们存储在 HKEY_LOCAL_MACHINE 中)。如果代码使用 HKEY_CURRENT_USER ,则不需要 ACL,这是因为它自动限制到在相关用户帐户下运行的进程。

您是否考虑了反向工程?
如果是,请考虑迷惑工具。有关详细信息,请参阅列出的迷惑工具列表,网址为 http://www.gotdotnet.com/team/csharp/tools/default.aspx(英文)。

注意 不要依赖迷惑工具来隐藏机密数据。迷惑工具使识别机密数据更困难,但不能解决问题。

您是否使用了委托?

任何代码可以将方法与委托相关联。这潜在包括以比您的代码低的信任级别运行的恶意代码。

您是否从不受信任的来源接受了委派?
如果是,请检查您使用具有 SecurityAction.PermitOnly 的安全权限对用于委派方法的代码访问权进行了限制。

您是否在调用委派之前使用了断言?
应避免这一点,这是因为您不知道委派代码在调用之前将如何进行操作。

代码访问安全

所有托管代码都服从代码访问安全权限需求。只有当在部分信任环境中使用代码或当代码访问安全策略未向调用代码授予完全信任时,许多问题才是明显的。

有关本部分中产生的问题的详细信息,请参阅模块 8 代码访问安全的实践。

使用下列审查点检查您是否适当且安全地使用了代码访问安全:

您是否支持部分信任调用方?

您是否限制了对公用类型和成员的访问?

您是否使用了声明安全?

您是否调用了断言?

您是否在应该的时候使用了权限要求?

您是否使用了链接请求?

您是否使用了 Deny 或 PermitOnly?

您是否使用了部分危险的权限?

您是否使用 /unsafe 选项进行了编译?

您是否支持部分信任调用方?

如果代码支持部分信任调用方,它甚至更有可能受到攻击,因此执行广泛且彻底的代码审查尤其重要。审查 Web 应用程序中的 <trust> 级别配置设置,以确定它是否在部分信任级别运行。如果是,则您为应用程序开发的程序集需要支持部分信任调用方。

下列问题帮助您识别潜在易受攻击区域:

程序集是否采用了强命名?
如果是,则默认安全策略确保它不能被部分信任调用方调用。公共语言运行库 (CLR) 发出对完全信任的明确链接要求。如果程序集没有进行采用强命名,则任何代码都可以调用它,除非您采取明确步骤限制调用方,例如通过明确要求完全信任。

注意 ASP.NET 应用程序调用的强命名程序集必须安装在全局程序集缓存中。

您是否使用了 APTCA?
如果强命名程序集包含 AllowPartiallyTrustedCallersAttribute,则部分受信任调用方可以调用您的代码。在此情况下,请检查您的程序集执行的任何资源访问或其他特权操作是否经过授权且是否使用其他代码访问安全请求进行了保护。如果使用 .NET Framework 类库访问资源,则全堆栈行走请求将自动发出并对调用代码进行授权,除非您的代码使用了 Assert 调用来防止堆栈行走。

您是否提供了对象参考?
检查方法返回和 ref 参数,以确定您的代码返回对象参考的位置。检查部分信任代码未提供从需要完全信任调用方的程序集获取的对象的参考。

您是否限制了对公用类型和成员的访问?

可以使用代码访问安全识别请求来限制对公用类型和成员的访问。这是减少程序集的攻击面的有效方法。

您是否使用身份请求来限制调用方?
如果您拥有只希望特定程序集在特定应用程序中使用的类或结构,则可以使用身份请求来限制调用方的范围。例如,可以使用带有 StrongNameIdentityPermission 的请求将调用方限制在一组特定程序集(此程序集使用与请求中的公钥对应的私钥进行签名)。

您是否使用了继承请求来限制子类?
如果知道只有特定代码应该从基本类继承,请检查类是否使用了具有 StrongNameIdentityPermission 的继承请求。

您是否使用了声明安全属性?

声明安全属性可以与工具(例如 Permview.exe)一起显示。这可以极大帮助您的程序集的客户和管理员了解您的代码的安全需求。

您是否请求了最低权限?
搜索“.RequestMinimum”字符串,以确定代码是否使用权限请求来指定其最低权限需求。您应该执行此操作,以清楚地说明程序集的权限需求。

您是否请求了可选权限或拒绝权限?
请搜索“.RequestOptional”和“.RequestRefuse”字符串。如果使用这两个操作之一来开发最低特权代码,请注意您的代码不再可以调用强命名程序集,除非它们使用 AllowPartiallyTrustedCallersAttribute 进行了标记。

您是否使用命令性安全,而非使用声明安全?
有时代码中的命令性检查是必需的,这是因为您需要应用逻辑以确定要请求哪些权限,或者因为您在请求中需要运行时变量。如果不需要特定逻辑,请考虑使用声明安全来说明程序集的权限需求。

您是否将类和成员级别特性混合在一起?
请不要这样做。成员特性(例如方法或属性中的成员特性)使用相同的安全操作来替换类级别特性,不要将它们混合在一起。

您是否调用了断言?

扫描代码,以获取 Assert 调用。这可以找到 Debug.Assert 的实例。在 CodeAccessPermission 对象中查找您的代码调用 Assert 的位置。当您断言代码访问权限时,将代码访问安全权限请求堆栈行走短路,这是危险的操作。您的代码采取哪些步骤确保恶意调用方不会利用断言来访问安全的资源或特权操作?审查下列问题:

您是否使用了请求、断言模式?
检查您的代码是否在 Assert 之前发出了 Demand。代码应该请求更细化的权限,以便在断言更广的权限(例如非托管代码权限)之前对调用方进行授权。

您是否将 Assert 调用与 RevertAssert 相匹配?
检查对 Assert 的每个调用是否与对 RevertAssert 的调用相匹配。当调用 Assert 的方法返回时,隐含删除 Assert,但是最佳操作是在 Assert 调用之后尽可能快地明确调用 RevertAssert

您是否缩短了断言期限?
检查您是否只为最小必需时间长度断言权限。例如,如果只在调用另一方法时需要使用 Assert 调用,请检查是否在方法调用之后是否立即调用了 RevertAssert

您是否在应该的时候使用了权限要求?

代码始终服从来自 .NET Framework 类库的权限请求检查,但是如果代码使用明确权限请求,请检查是否会相应执行此操作。搜索代码,以查找“.Demand”字符串,以便识别声明权限请求和命令性权限请求,然后审查下列问题:

您是否缓存了数据?
如果是,请检查代码是否在访问缓存数据之前发出了适当的权限请求。例如,如果从文件获取数据,并且您要确保调用代码经过授权以访问从中填充缓存的文件,请在访问缓存数据之前请求 FileIOPermission

您是否公开了自定义资源或特权操作?
如果代码通过非托管代码公开自定义资源或特权操作,请检查它是否发出了适当的权限请求,根据资源的性质,此权限请求可能是内置权限类型或自定义权限类型。

您是否足够快地发出了请求?
请检查在访问资源或执行特权操作之前是否发出了权限请求。不要访问资源然后再对调用方授权。

您是否发出了冗余请求?
使用 .NET Framework 类库的代码服从权限请求。您的代码不需要发出相同请求。这会导致重复和浪费的堆栈行走。

您是否使用了链接请求?

与常规请求不同,链接请求只检查立即调用方。它们不执行完全堆栈行走,因此,使用链接请求的代码易受到引诱攻击。有关引诱攻击的信息,请参阅模块 8 代码访问安全的实践中的“链接请求”。

搜索您的代码以查找“.LinkDemand”字符串,以便识别使用链接请求的位置。它们只能声明性使用。下面的代码片段显示了一个示例:

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

有关本部分中产生的问题的详细信息,请参阅模块 8 代码访问安全的实践中的“链接请求”。下列问题帮助您审查代码中链接请求的使用:

为什么使用链接请求?
防御方法是尽可能避免链接请求。不要只使用它们来提高性能和消除完全堆栈行走。与其他 Web 应用程序性能问题(例如网络滞后和数据库访问)的成本相比,堆栈行走的成本不高。只有当您了解并可以限制哪一个代码可调用您的代码时,链接请求才是安全的。

您是否信任调用方?
使用链接请求时,依赖调用方来防止引诱攻击。只有当了解并可以将直接调用方的准确集合限制在您的代码中、并可以信任这些调用方以授权其调用方时,链接请求才是安全的。

您是否调用了使用链接请求保护的代码?
如果是,您的代码是否通过从代码的调用方请求安全权限来提供授权?传递给您的方法的参数是否可以穿过您的方法传递给您调用的代码?如果是,它们是否会恶意影响您调用的代码?

您在方法和类级别是否使用了链接请求?
当您将链接请求添加到方法时,它改写类中的链接请求。检查此方法是否还包括类级别链接请求。

您是否使用了未封装的类中的链接请求?
派生类型不继承链接请求,且在派生类型中调用已改写的方法时,不使用链接请求。如果改写需要使用链接请求保护的方法,请将链接请求应用于已改写的方法。

您是否使用了链接请求以保护结构?
链接请求不防止不受信任的调用方构建结构。这是因为不为结构自动生成默认构造函数,因此只有当使用明确构造函数时才应用结构级别链接请求。

您是否使用了明确接口?
搜索 Interface 关键字以识别接口。如果这样做,请检查方法实现是否使用链接请求进行了标记。如果进行了标记,请检查接口定义是否包含相同的链接请求。否则,调用方可能绕过链接请求。

您是否使用了潜在危险的权限?

请检查下列权限类型是否只授权给高度受信任的代码。它们中的大多数没有自己的专用权限类型,而是使用一般 SecurityPermission 类型。您应该严密检查使用这些类型的代码,以确保将风险降到最低。另外,使用这些权限必须具有很好的原因。

表 21.3:危险的权限

权限 说明

SecurityPermission.UnmanagedCode

代码可以 调用非托管代码。

SecurityPermission.SkipVerification

程序集内的代码不再必须验证为安全类型。

SecurityPermission.ControlEvidence

代码可以提供其自己的证据,供安全策略评估使用。

SecurityPermission.ControlPolicy

代码可以查看和更改策略。

SecurityPermission.SerializationFormatter

代码可以使用序列化。

SecurityPermission.ControlPrincipal

代码可以操纵用于授权的主要对象。

ReflectionPermission.MemberAccess

代码可以通过反射调用某一类型的私有成员。

SecurityPermission.ControlAppDomain

代码可以创建新的应用程序域。

SecurityPermission.ControlDomainPolicy

代码可以更改域策略。

您是否使用 /unsafe 选项进行了编译?

使用 Visual Studio .NET 检查项目属性,以确定“允许不安全代码块”是否设置为“True”。这可以设置 /unsafe 编译器标志,它告诉编译器代码包含不安全的块,并请求在程序集内放置最低 SkipVerification 权限。

如果使用 /unsafe 编译,请检查需要这样做的原因。如果原因合法,请特别小心检查潜在漏洞的源代码。

非托管代码

由于不断增加的安全风险,需要对调用非托管代码(包括 Win32 DLLs 和 COM 对象)的代码给予特别关注。非托管代码不是可验证的安全类型,它有可能导致缓冲区溢出。来自非托管代码的资源访问不服从代码访问安全检查。这是托管包装类的责任。

通常,不应该将非托管代码直接公开给部分受信任调用方。有关本部分中产生的问题的详细信息,请参阅模块 7 构建安全的程序集 和模块 8 代码访问安全的实践中的“非托管代码”部分。

使用下列检查问题验证非托管代码的使用:

您是否断言了非托管代码权限?

如果是,请检查代码在调用 Assert 方法之前是否请求了适当的权限,以确保所有调用方都经过授权来访问非托管代码所公开的资源或操作。例如,下面的代码片段显示如何请求自定义加密权限以及然后断言非托管代码权限:

// 请求自定义 EncryptionPermission。
            (new EncryptionPermission(
            EncryptionPermissionFlag.Encrypt, storeFlag)).Demand();
            // 断言非托管代码权限。
            (new SecurityPermission(SecurityPermissionFlag.UnmanagedCode)).Assert();
            // 现在使用 P/Invoke 调用非托管 DPAPI 函数。
            

有关详细信息,请参阅模块 8 代码访问安全的实践中的“Assert 和 RevertAssert”。

您是否使用了 SuppressUnmanagedCodeAttribute?
此属性抑制当托管代码调用非托管代码时自动发出的对非托管代码权限的请求。如果使用此属性批注 P/Invoke 方法或 COM interop 接口,请确保使用安全权限请求保护了非托管代码调用的所有代码路径,以便授权调用方。另外,请检查此属性在方法级别使用,而不是在类级别使用。

注意 添加 SupressUnmanagedCodeSecurityAttribute 将 interop 层所发出的对 UnmanagedCode 权限的明确请求返回到 LinkDemand。您的代码易受到引诱攻击。

非托管入口点是否公开可见?
请检查非托管代码入口点是否标记为“专用”或“内部”。应该强制调用方调用对非托管代码进行封装的托管包装方法。

您是否防止了缓冲区溢出?
非托管代码易受到输入攻击(例如缓冲区溢出)。非托管代码 API 应该检查提供的参数的类型和长度。但是,由于您可能不拥有非托管源,所以不能依赖此检查。因此,托管包装代码必须严格检查输入和输出参数。有关详细信息,请参阅本模块中的缓冲区溢出

注意 所有适用于 C 和 C++ 的代码审查规则和规定都适用于非托管代码。

您是否对枚举类型进行了范围检查?
请在将枚举值传递给本机方法之前验证所有枚举值是否在范围中。

您是否对非托管代码方法使用了命名惯例?
所有非托管代码应该在具有下列名称的包装类中:NativeMethodsUnsafeNativeMethodsSafeNativeMethods。您必须全面审查 UnsafeNativeMethods 中的所有代码以及传递给本机 API 的参数,以查找安全漏洞。

您是否调用了潜在危险的 API?
您应该能够调整所有 Win32 API 调用的使用。危险的 API 包括:

切换安全上下文的线程函数

访问令牌函数,它可以更改或公开有关安全令牌的信息

凭据管理函数,包括创建令牌的函数

可以解密和访问私钥的加密 API 函数

可以读写内存的内存管理函数

可以访问系统机密的 LSA 函数

ASP.NET 页面和控件

使用此部分中的审查问题来审查 ASP.NET 页面和控件。有关本部分中产生的问题的详细信息,请参阅模块 10 构建安全的 ASP.NET 页面和控件。

您是否禁用了详细错误消息?

您是否禁用了跟踪?

您是否验证了表格字段输入?

您是否易受到 XSS 攻击?

您是否验证了查询字符串和 Cookie 输入?

您是否依赖 HTTP 头来确保安全?

您是否确保了视图状态的安全?

您是否防止了 XSS?

您的 global.asax 事件处理程序是否安全?

您是否提供了适当的权限?

您是否禁用了详细错误消息?

如果让例外传播到应用程序边界之外,则 ASP.NET 会将详细信息返回给调用方。这包括完全堆栈跟踪和其他对攻击者有用的信息。检查 <customErrors> 元素,确保模式属性设置为“On”或“RemoteOnly”。

<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,请检查您的代码是否使用了接受 bool 参数(此参数防止交叉应用程序映射)的 HttpRequest.MapPath 的替代。

try
            {
            string mappedPath = Request.MapPath( inputPath.Text,
            Request.ApplicationPath, false);
            }
            catch (HttpException)
            {
            // 尝试了交叉应用程序映射。
            }

有关详细信息,请参阅模块 10 构建安全的 ASP.NET 页面和控件中的“使用 MapPath”部分。

您如何验证数据类型?
检查您的代码是否验证了从邮寄表格字段以及其他 Web 输入(如查询字符串)表格接收的数据的数据类型。对于非字符串数据,请检查代码是否使用 .NET Framework 类型系统执行类型检查。可以将字符串输入转换为强键入对象,并捕获任何类型转换例外。例如,如果字段包含日期,则使用它构建 System.DateTime 对象。如果它包含以年份表示的年龄,则将它转换为使用 Int32.ParseSystem.Int32 对象,并捕获格式例外。

您如何验证字符串类型?
检查是否使用正则表达式对长度以及可接受字符和模式集验证了输入字符串。可以使用 RegularExpressionValidator 验证控件或直接使用 RegEx 类。不要搜索无效数据,只搜索您已知正确的信息格式。

您是否使用了验证控件?
如果使用验证控件(例如 RegularExpressionValidator、RequiredFieldValidator、CompareValidator、RangeValidator 或 CustomValidator),请检查您是否未禁用服务器方验证和不纯粹依赖客户端方验证。

您是否依赖客户端方验证?
请不要这样做。只使用客户端方验证来提高用户经验。检查是否在服务器验证了所有输入。

您是否易受到 XSS 攻击?

一定要审查网页中是否有 XSS 漏洞。有关详细信息,请参阅本模块前面的跨站点脚本 (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 的属性,例如设置“安全”位或域,则 Application_EndRequest 是执行此操作的正确位置。

Application_AuthenticateRequest。它执行用户身份验证。

Application_Error。调用此事件处理程序时的安全上下文可以对写入 Windows 事件日志产生影响。安全上下文可以是进程帐户或模拟的帐户。

受保护的 void Session_End。此事件的启动没有确定性,且只对进程中会话状态模式启动此事件。

您是否提供了适当的权限?

审查下列问题以验证您的授权方法:

您是否将受限区域的网站与公共访问区域的网站分开?
如果 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 Services。

您是否公开了受限操作或数据?

您是否对调用方进行了授权?

您是否限制了特权操作?

您是否使用了自定义身份验证?

您是否验证了所有输入?

您是否验证了 SOAP 头?

您是否公开了受限操作或数据?

如果 Web 服务公开受限操作或数据,请检查此服务是否对调用方进行身份验证。可以使用平台身份验证机制(例如 NTLM、Kerberos、基本身份验证或客户端 X.509 证书),也可以在 SOAP 头中传递身份验证令牌。

如果传递身份验证令牌,可以使用 Web Service Enhancements (WSE),以便以符合正在产生的 WS 安全标准的方式使用 SOAP 头。

您是否对调用方进行了授权?

选择 .NET Framework(例如 URL 身份验证、文件授权、.NET 角色)或平台选项(例如文件 ACL)所提供的适当授权方案。

您是否限制了特权操作?

代码访问安全策略的信任级别确定 Web 服务可以访问的资源类型。在 Machine.config 或 Web.config 中检查 <trust> 元素配置。

您是否使用了自定义身份验证?

请使用 Web Service Enhancements (WSE) 提供的功能,而不要创建自己的身份验证方案。

您是否验证了所有输入?

请检查:如果从当前信任边界外的来源收到输入,在使用这些输入或将它们传递给下游组件或数据库之前,所有公开的 Web 方法是否验证了它们的输入参数。

您是否验证了 SOAP 头?

如果在应用程序中使用自定义 SOAP 头,请检查信息是否未被篡改或重放。对头信息进行数字签名,以确保它未被篡改。可以使用 WSE 帮助以标准方法对 Web 服务消息进行签名。

检查 SoapException SoapHeaderException 对象是否用来得体地处理错误和向客户端提供最低必需信息。验证是否为解决问题而相应记录了例外。

接受服务的组件

本部分列出您在审查企业服务应用程序中使用的接受服务的组件时应该考虑的关键审查点。有关本部分中产生的问题的详细信息,请参阅模块 11 构建安全服务型组件。

您是否使用了程序集级别元数据?

您是否防止了匿名访问?

您是否使用了受限模拟级别?

您是否使用了基于角色的安全?

您是否使用了方法级别授权?

您是否使用了对象构造函数字符串?

您是否在中间层中进行了审核?

您是否使用了程序集级别元数据?

检查您是否使用了程序集级别元数据来定义企业服务安全设置。使用 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 及更高版本中,不向所有用户授予模拟特权。

如果您的组件位于服务器应用程序中,则上述程序集级别属性在组件使用企业服务注册时控制组件的原始配置。

如果您的组件位于库应用程序中,则客户端进程确定模拟级别。如果客户端是 ASP.NET Web 应用程序,请检查 Machine.config 文件中 <processModel> 元素的 comImpersonationLevel 设置。

您是否使用了基于角色的安全?

通过审查下列问题,检查您的代码是否正确使用了基于角色的安全,以防止未经授权的访问:

是否启用了基于角色的安全?
检查是否启用了基于角色的安全。默认情况下,Windows 2000 中禁用它。检查您的代码是否包括以下属性:

[assembly:ApplicationAccessControl(true)]
            

您是否使用了组件级别访问检查?
如果在接口、组件或方法级别使用 COM+ 角色(而不仅仅用于限制对应用程序的访问),则 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 方法传递到计算机时对数据进行加密。

您是否提供了默认构建字符串?
如果数据在某中程度上是敏感的,请不要执行此操作。

您是否在中间层中进行了审核?

您应该跨分布式应用程序的层进行审核。检查服务组件是否记录了操作和事务。可以通过 SecurityCallContext 对象获得原始调用方身份。只有当使用以下属性为进程级别和组件级别的检查配置应用程序的安全级别时,它才可用:

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

远程

本部分列出审查使用 .NET Remoting 的代码时应该考虑的关键审查点。有关本部分中产生的问题的详细信息,请参阅模块 13 构建安全的远程组件。

您是否将对象作为参数传递?

您是否使用了自定义身份验证和主要对象?

您如何配置代理凭据?

您是否将对象作为参数传递?

如果使用 TcpChannel 且您的组件 API 接受自定义对象参数,或者如果通过调用上下文传递自定义组件,则您的代码有两个安全漏洞。

如果作为参数传递的对象派生于 System.MarshalByRefObject,则它通过引用传递。这种情况下,此对象需要 URL,以支持回调到客户端。客户端 URL 可能受欺骗,会导致回调到备用计算机。

如果作为参数传递的对象支持序列化,则对象通过值传递。在此情况下,检查您的代码在服务器上取消序列化每个字段项时是否对每个字段项进行了验证,以避免恶意数据注入。

要防止自定义对象通过引用或通过值传递到远程组件,请将服务器方的格式化程序信道接收中的 TypeFilterLevel 属性设置为 TypeFilterLevel.Low

要找到在调用上下文中传递的对象,请搜索“ILogicalThreadAffinative”字符串。只有实现此接口的对象可以在调用上下文中传递。

您是否使用了自定义身份验证和主要对象?

如果使用了自定义身份验证,您是否依赖从客户端传递的主要对象?这存在潜在危险,因为恶意代码会创建包含扩展角色的主要对象以提升特权。如果使用此方法,请检查您是否只将它与带外机制(例如限制可以连接到您的组件的客户端计算机的 IPSec 策略)一同使用。

您如何配置代理凭据?

审查客户端代码如何配置远程代理中的凭据。如果使用了明确凭据,在何处维护这些凭据?它们应该加密,并存储在安全位置(例如受限的注册表项)。它们不应该以纯文本形式进行硬编码。理想情况下,您的客户端代码应该使用客户端进程令牌和使用默认凭据。

数据访问代码

本部分列出您在审查数据访问代码时应该考虑的关键审查点。有关本部分中产生的问题的详细信息,请参阅模块 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 例外?

检查所有数据访问代码是否放置在 try/catch 块中,以及此代码是否根据您使用的 ADO .NET 数据提供程序处理 SqlExceptionsOleDbExceptions OdbcExceptions

您是否关闭了数据库连接?

检查您的代码(例如发生异常时)没有因开放数据库连接而受到攻击。检查代码是否关闭了最后块中的连接,或者使用如下所示的语句在 C# 中构建了连接对象。这将自动确保它已关闭。

using ((SqlConnection conn = new SqlConnection(connString)))
{
conn.Open();
// 如果生成异常或如果控制溢出,则将关闭连接
// 保留正常使用语句的范围。
}

小结

除了将重点放在可导致安全漏洞的识别上,安全代码审查类似于常规代码审查或检查。增加的好处是安全缺陷的消除通常使您的代码更强大。

本模块已经讨论了如何审查托管代码中是否有主要的安全问题,包括 XSS、SQL 注入和缓冲区溢出。它还讨论了如何识别出可导致安全漏洞和成功攻击的其他更细微的缺陷。

安全代码审查不是万能药。但是,它们会非常有效,且应该作为开发生命周期中的常规里程碑。


http://www.microsoft.com/china/technet/security/guidance/secmod94.mspx