代码审查
本页内容
本模块内容 | |
目标 | |
适用范围 | |
如何使用本模块 | |
FxCop | |
执行文本搜索 | |
跨站点脚本 (XSS) | |
SQL 注入 | |
缓冲区溢出 | |
托管代码 | |
代码访问安全 | |
非托管代码 | |
ASP.NET 页面和控件 | |
Web 服务 | |
接受服务的组件 | |
远程 | |
数据访问代码 | |
小结 | |
其他资源 |
本模块内容
安全代码审查的目的是要识别出会导致安全问题和事故的不安全编码技术和漏洞。虽然可能很耗时,但代码审查必须是项目开发周期中的常规事件,这是因为在开发时修复安全缺陷会比以后在产品部署或维护修复周期中再做这项工作节省大量的成本和工作量。
本模块帮助您审查使用 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 中的其他安全模块。 参考下列这些模块,以便提供有关本模块中分级显示的审查问题的其他信息。
| ||||||||||||||||||
• | 使用其他检查表:
|
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 属性的使用。 |
识别输出输入的代码
从浏览器查看页面输出来源,以查看代码是否在属性中。如果是,则注入下面的代码,然后重新测试以查看输出。
"οnmοuseοver= 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 属性(例如 src、lowsrc、style 和 href)与上述标记一起使用会导致 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 |
"(双引号) |
" |
" |
" |
/u0022 |
'(单引号) |
' |
' |
' |
/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> 元素所配置的 requestEncoding 和 responseEncoding 属性进行了设置。
<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 属性,而不是 innerHTML。innerText 属性将内容呈现为安全,并确保不执行脚本。
更多信息
有关 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. | 查找访问数据库的代码。 |
2. | 检查代码是否使用参数化存储过程。 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 语句中的参数。 select status from Users where UserName=@userName 检查下面的方法是否未使用,其中输入直接用于构建使用字符串串联的可执行 SQL 语句: string sql = "select status from Users where UserName='" + txtUserName.Text + "'"; |
4. | 检查代码是否尝试筛选输入。 |
缓冲区溢出
当您审查缓冲区溢出的代码时,请将审查重点放在通过 P/Invoke 或 COM interop 层调用非托管代码的代码上。由于当访问数组时自动检查数组界限,托管代码本身受缓冲区溢出危害要小得多。只要调用 Win32 DLL 或 COM 对象,就应该严格检查 API 调用。
下面的过程帮助您找到缓冲区溢出漏洞:
1. | 查找对非托管代码的调用。 |
2. | 检查传递给非托管 API 的字符串参数。 void SomeFunction( char *pszInput ) { char szBuffer[10]; // 注意,没有长度检查。输入直接复制到缓冲区 // 应该检查长度或使用 strncpy。 strcpy(szBuffer, pszInput); . . . } 注意 如果使用 strncpy,缓冲区溢出仍会发生,这是因为它不检查目标字符串中的多余空格,它只限制复制的字符串的数量。 如果由于不拥有非托管代码而不能检查它,则可以通过故意传递长输入字符串和无效参数来严格测试 API。 |
3. | 检查文件路径长度。 |
4. | 检查输出字符串。 |
5. | 检查数组界限。 |
6. | 检查非托管代码是否是使用 /GS 开关编译的。 |
托管代码
使用本部分中的审查问题对整个托管源代码库进行分析。无论程序集的类型如何,这些审查问题都适用。本部分帮助您识别常见托管代码漏洞。有关本部分中产生的问题以及说明漏洞的代码示例的详细信息,请参阅模块 7 构建安全的程序集。
如果托管代码使用明确代码访问安全功能,请参阅本模块后面的代码访问安全,以获取其他审查点。下列审查问题可以帮助您识别托管代码漏洞:
• | 您的类设计安全吗? |
• | 您是否创建了线程? |
• | 您是否使用了序列化? |
• | 您是否使用了反射? |
• | 您是否处理了例外? |
• | 您是否使用了加密? |
• | 您是否存储了机密? |
• | 您是否使用了委托? |
您的类设计安全吗?
程序集只与它包含的类和其他类型一样安全。下列问题可以帮助您审查类设计的安全性:
• | 您是否限制了类型和成员可见性? |
• | 非基本类是否已封装? 对于公用基本类,可以使用代码访问安全继承需求来限制可以从类继承的代码。这是一个好的深度防御方法。 |
• | 您是否使用了属性来公开字段? |
• | 您是否使用了只读属性? |
• | 您是否使用了虚拟内部方法? |
• | 您是否实现 IDisposable? |
您是否创建了线程?
多线程代码容易产生敏感的、与计时相关的缺陷或赛跑条件,它们会导致安全漏洞。要找到多线程代码,请搜索文本“线程”的源代码,以识别在何处创建新线程对象,如下面的代码片段所示:
Thread t = new Thread(new ThreadStart(someObject.SomeThreadStartMethod));
下列审查问题可以帮助您识别潜在线程漏洞:
• | 您的代码是否缓存了安全检查的结果? |
• | 您的代码是否进行了模拟? |
• | 您的代码是否包含静态类构造函数? |
• | 您是否同步了 Dispose 方法? |
您是否使用了序列化?
支持序列化的类标记了 SerializableAttribute,或从 ISerializable 派生。要找到支持序列化的类,请对“可序列化”字符串执行文本搜索。然后审查代码中是否有下列问题:
• | 类是否包含敏感数据? 如果类需要将敏感数据序列化,请检查保护数据的方法。首先考虑对数据加密。 |
• | 类是否实现了 ISerializable? |
• | 类是否验证了数据流? |
您是否使用了反射?
要帮助找到使用反射的代码,请搜索“System.Reflection”— 这是包含反射类型的名称空间。如果确实使用了反射,请审查下列问题,以帮助识别潜在漏洞:
• | 您是否动态加载了程序集? |
• | 您是否在运行时动态创建了代码? |
• | 您是否在其他类型上使用了反射? |
您是否处理了例外?
强大的代码需要安全例外处理,以确保记录了足够的例外详细信息,以便帮助进行问题诊断和帮助防止内部系统详细信息暴露给客户端。审查下列问题,以帮助识别潜在例外处理漏洞:
• | 您以前是否曾失败? |
• | 您如何处理例外? |
• | 您是否记录了例外详细信息? |
• | 您是否使用了例外筛选器? 有关例外筛选器漏洞的示例,请参阅模块 7 构建安全的程序集中的“例外管理”。 |
您是否使用了加密?
如果是,请检查您的代码不执行其自己的加密例程。相反,代码应该使用 System.Security.Cryptography 名称空间或使用 Win32 加密(例如数据保护应用程序编程接口 (DPAPI))。审查下列问题,以帮助识别潜在潜在加密相关的漏洞:
• | 您是否使用了对称加密? |
• | 您是否使用了最大可能大小的密钥? |
• | 您是否使用了哈希? |
• | 您是否出于加密目的生成随机编号? |
您是否存储了机密?
如果程序集存储了机密,请检查设计,以检查绝对需要存储机密。如果必须存储机密,请审查下列问题,以便尽可能安全地进行此操作:
• | 您是否在内存中存储了机密? |
• | 您是否在 Web.config 或 Machine.config 中存储纯文本密码或 SQL 连接字符串? |
• | 您如何对机密加密? |
• | 您是否在注册表中了存储机密? |
• | 您是否考虑了反向工程? 注意 不要依赖迷惑工具来隐藏机密数据。迷惑工具使识别机密数据更困难,但不能解决问题。 |
您是否使用了委托?
任何代码可以将方法与委托相关联。这潜在包括以比您的代码低的信任级别运行的恶意代码。
• | 您是否从不受信任的来源接受了委派? |
• | 您是否在调用委派之前使用了断言? |
代码访问安全
所有托管代码都服从代码访问安全权限需求。只有当在部分信任环境中使用代码或当代码访问安全策略未向调用代码授予完全信任时,许多问题才是明显的。
有关本部分中产生的问题的详细信息,请参阅模块 8 代码访问安全的实践。
使用下列审查点检查您是否适当且安全地使用了代码访问安全:
• | 您是否支持部分信任调用方? |
• | 您是否限制了对公用类型和成员的访问? |
• | 您是否使用了声明安全? |
• | 您是否调用了断言? |
• | 您是否在应该的时候使用了权限要求? |
• | 您是否使用了链接请求? |
• | 您是否使用了 Deny 或 PermitOnly? |
• | 您是否使用了部分危险的权限? |
• | 您是否使用 /unsafe 选项进行了编译? |
您是否支持部分信任调用方?
如果代码支持部分信任调用方,它甚至更有可能受到攻击,因此执行广泛且彻底的代码审查尤其重要。审查 Web 应用程序中的 <trust> 级别配置设置,以确定它是否在部分信任级别运行。如果是,则您为应用程序开发的程序集需要支持部分信任调用方。
下列问题帮助您识别潜在易受攻击区域:
• | 程序集是否采用了强命名? 注意 ASP.NET 应用程序调用的强命名程序集必须安装在全局程序集缓存中。 |
• | 您是否使用了 APTCA? |
• | 您是否提供了对象参考? |
您是否限制了对公用类型和成员的访问?
可以使用代码访问安全识别请求来限制对公用类型和成员的访问。这是减少程序集的攻击面的有效方法。
• | 您是否使用身份请求来限制调用方? |
• | 您是否使用了继承请求来限制子类? |
您是否使用了声明安全属性?
声明安全属性可以与工具(例如 Permview.exe)一起显示。这可以极大帮助您的程序集的客户和管理员了解您的代码的安全需求。
• | 您是否请求了最低权限? |
• | 您是否请求了可选权限或拒绝权限? |
• | 您是否使用命令性安全,而非使用声明安全? |
• | 您是否将类和成员级别特性混合在一起? |
您是否调用了断言?
扫描代码,以获取 Assert 调用。这可以找到 Debug.Assert 的实例。在 CodeAccessPermission 对象中查找您的代码调用 Assert 的位置。当您断言代码访问权限时,将代码访问安全权限请求堆栈行走短路,这是危险的操作。您的代码采取哪些步骤确保恶意调用方不会利用断言来访问安全的资源或特权操作?审查下列问题:
• | 您是否使用了请求、断言模式? |
• | 您是否将 Assert 调用与 RevertAssert 相匹配? |
• | 您是否缩短了断言期限? |
您是否在应该的时候使用了权限要求?
代码始终服从来自 .NET Framework 类库的权限请求检查,但是如果代码使用明确权限请求,请检查是否会相应执行此操作。搜索代码,以查找“.Demand”字符串,以便识别声明权限请求和命令性权限请求,然后审查下列问题:
• | 您是否缓存了数据? |
• | 您是否公开了自定义资源或特权操作? |
• | 您是否足够快地发出了请求? |
• | 您是否发出了冗余请求? |
您是否使用了链接请求?
与常规请求不同,链接请求只检查立即调用方。它们不执行完全堆栈行走,因此,使用链接请求的代码易受到引诱攻击。有关引诱攻击的信息,请参阅模块 8 代码访问安全的实践中的“链接请求”。
搜索您的代码以查找“.LinkDemand”字符串,以便识别使用链接请求的位置。它们只能声明性使用。下面的代码片段显示了一个示例:
[StrongNameIdentityPermission(SecurityAction.LinkDemand, PublicKey="00240000048...97e85d098615")] public static void SomeOperation() {}
有关本部分中产生的问题的详细信息,请参阅模块 8 代码访问安全的实践中的“链接请求”。下列问题帮助您审查代码中链接请求的使用:
• | 为什么使用链接请求? |
• | 您是否信任调用方? |
• | 您是否调用了使用链接请求保护的代码? |
• | 您在方法和类级别是否使用了链接请求? |
• | 您是否使用了未封装的类中的链接请求? |
• | 您是否使用了链接请求以保护结构? |
• | 您是否使用了明确接口? |
您是否使用了潜在危险的权限?
请检查下列权限类型是否只授权给高度受信任的代码。它们中的大多数没有自己的专用权限类型,而是使用一般 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? 注意 添加 SupressUnmanagedCodeSecurityAttribute 将 interop 层所发出的对 UnmanagedCode 权限的明确请求返回到 LinkDemand。您的代码易受到引诱攻击。 | ||||||||||||
• | 非托管入口点是否公开可见? | ||||||||||||
• | 您是否防止了缓冲区溢出? 注意 所有适用于 C 和 C++ 的代码审查规则和规定都适用于非托管代码。 | ||||||||||||
• | 您是否对枚举类型进行了范围检查? | ||||||||||||
• | 您是否对非托管代码方法使用了命名惯例? | ||||||||||||
• | 您是否调用了潜在危险的 API?
|
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? try { string mappedPath = Request.MapPath( inputPath.Text, Request.ApplicationPath, false); } catch (HttpException) { // 尝试了交叉应用程序映射。 } 有关详细信息,请参阅模块 10 构建安全的 ASP.NET 页面和控件中的“使用 MapPath”部分。 |
• | 您如何验证数据类型? |
• | 您如何验证字符串类型? |
• | 您是否使用了验证控件? |
• | 您是否依赖客户端方验证? |
您是否易受到 XSS 攻击?
一定要审查网页中是否有 XSS 漏洞。有关详细信息,请参阅本模块前面的跨站点脚本 (XSS)。
您是否验证了查询字符串和 Cookie 输入?
检查您的代码是否对 URL 查询字符串和从 Cookie 提取的输入字段所传递的输入字段进行了验证。要找到易受攻击的代码,请搜索下列文本字符串:
• | “Request.QueryString” |
• | “Request.Cookies” |
检查是否像对表格字段那样(请参阅上一部分“您是否验证了表格字段输入?”)使用键入的对象及正则表达式对输入的类型、范围、格式和长度进行了验证。另外,将 HTML 或 URL 编码考虑为从用户输入派生的任何输出,因为这将否定任何可导致 XSS 缺陷的无效构建。
您是否确保了视图状态的安全?
如果应用程序使用视图状态,它是否防篡改?审查下列问题:
• | 是否在应用程序级别启用了视图状态保护? <pages enableViewState="true" enableViewStateMac="true" /> |
• | 您是否在每页基础上改写了视图状态保护? |
• | 您是否改写了代码中的视图状态保护? |
您的 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。此事件的启动没有确定性,且只对进程中会话状态模式启动此事件。 |
您是否提供了适当的权限?
审查下列问题以验证您的授权方法:
• | 您是否将受限区域的网站与公共访问区域的网站分开? |
• | 您如何保护对受限页面的访问? 您是否已经配置了 <authorization> 元素以便指定哪些用户和用户组可以访问特定页面? |
• | 您如何保护对页面类的访问? |
• | 您是否使用了 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 设置。
您是否使用了基于角色的安全?
通过审查下列问题,检查您的代码是否正确使用了基于角色的安全,以防止未经授权的访问:
• | 是否启用了基于角色的安全? [assembly:ApplicationAccessControl(true)] |
• | 您是否使用了组件级别访问检查? [assembly:ApplicationAccessControl(AccessChecksLevel= AccessChecksLevelOption.ApplicationComponent)] 另外,检查每个类是否使用 ComponentAccessControl 属性按如下进行了批注: [ComponentAccessControl(true)] public class YourServicedComponent :ServicedComponent { } |
• | 您是否在代码中执行了角色检查? |
您是否使用了对象构造函数字符串?
搜索您的代码来查找“ConstructionEnabled”,以便找到使用对象构建字符串的类。
[ConstructionEnabled(Default="")] public class YourServicedComponent :ServicedComponent, ISomeInterface
如果使用对象构造函数字符串,请审查下列问题:
• | 您是否在构造函数字符串中存储了敏感数据? |
• | 您是否提供了默认构建字符串? |
您是否在中间层中进行了审核?
您应该跨分布式应用程序的层进行审核。检查服务组件是否记录了操作和事务。可以通过 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 属性是如何设置的。
• | 您是否对连接字符串进行了加密? |
• | 您是否使用了空密码? |
• | 您是否使用了 sa 帐户或其他高特权帐户? |
• | 您是否使用了 Persist Security Info? |
您如何限制未经授权的代码?
如果您已写入了数据访问类库,如何防止未经授权的代码访问您的库以访问数据库?一个方法是使用 StrongNameIdentityPermission 请求将调用代码限制为使用特定强名称私钥进行了签名的代码。
您如何确保数据库中敏感数据的安全?
如果在数据库中存储了敏感数据(例如信用卡号),如何确保数据的安全?您应该检查它是否使用强对称加密算法(例如 3DES)进行了加密。
如果使用此方法,您如何确保 3DES 加密密钥的安全?您的代码应该使用 DPAPI 对 3DES 加密密钥进行加密,并在受限位置(例如注册表)中存储了加密密钥。
您是否处理了 ADO .NET 例外?
检查所有数据访问代码是否放置在 try/catch 块中,以及此代码是否根据您使用的 ADO .NET 数据提供程序处理 SqlExceptions、OleDbExceptions 或 OdbcExceptions。
您是否关闭了数据库连接?
检查您的代码(例如发生异常时)没有因开放数据库连接而受到攻击。检查代码是否关闭了最后块中的连接,或者使用如下所示的语句在 C# 中构建了连接对象。这将自动确保它已关闭。
using ((SqlConnection conn = new SqlConnection(connString))) { conn.Open(); // 如果生成异常或如果控制溢出,则将关闭连接 // 保留正常使用语句的范围。 }
小结
除了将重点放在可导致安全漏洞的识别上,安全代码审查类似于常规代码审查或检查。增加的好处是安全缺陷的消除通常使您的代码更强大。
本模块已经讨论了如何审查托管代码中是否有主要的安全问题,包括 XSS、SQL 注入和缓冲区溢出。它还讨论了如何识别出可导致安全漏洞和成功攻击的其他更细微的缺陷。
安全代码审查不是万能药。但是,它们会非常有效,且应该作为开发生命周期中的常规里程碑。