执行文本搜索
为了辅助审查过程的进行,可以使用您熟悉的文本搜索工具在文件中定位字符串。这种工具使您可很快地定位有缺陷的代码。本单元中后面提出的许多审查问题都指出了查找特定缺陷时可以搜索的最佳字符串。
您可能已经有了惯用的搜索工具。如果没有,您可以使用 Visual Studio .NET 中的 Find in Files 工具,或者 Findstr 命令行工具,后者包含在 Microsoft Windows 操作系统中。
在您对源代码进行逐行的详细分析之前,首先快速搜索整个基本代码,识别硬编码的密码、帐户名和数据库连接字符串。扫描代码并搜索常见的字符串模式,如以下的一些:“key”、“secret”、“password”、“pwd”和“connectionstring”。
例如,要在应用程序 Web 目录中搜索字符串“password”,应该从命令提示符使用 Findstr 工具,如下所示:
Findstr /S /M /I /d:c:\projects\yourweb "password" *.*
| • | |
| • | |
| • | |
| • | /D:dir — 搜索分号分隔的目录列表。如果要搜索的文件路径包含空格,应该用双引号将路径括起来。 |
您可以用常见搜索字符串创建一个文本文件。Findstr 然后可以从文本文件中读取搜索的字符串,如下所示。从包含 .aspx 文件的目录运行以下命令。
Findstr /N /G:SearchStrings.txt *.aspx
/N 在发现匹配时输出对应的行号。/G 指示包含搜索字符串的文件。在本例中,搜索所有 ASP.NET 页 (*.aspx) 中是否有 SearchStrings.txt 所包含的字符串。
您还可以将 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 缺陷:
| • | |
| • | |
| • | |
| • | |
| • | |
| • | |
| • | |
| • | |
| • | 检查是否使用 innerText 和 innerHTML 属性。 |
从浏览器查看页输出的源代码,看是否您的代码位于一个属性中。如果确实如此,注入以下代码并重新测试,以查看输出。
"onmouseover= alert('hello');"
开发人员常用的一个技术是筛选 < and > 字符。如果所审查的代码筛选了这些字符,那么可以改用以下代码测试:
&{alert('hello');}
如果代码不筛选这些字符,那么可以通过使用以下脚本测试代码:
<script>alert(document.cookie);</script>;
在使用这个脚本之前可能必须要添加一个结束标记,如下所示。
"></a><script>alert(document.cookie);</script>
在 .aspx 源代码和任何其他为应用程序开发的程序集包含的代码中搜索“.Write”字符串。这将定位所有出现的 Response.Write,以及任何可能通过响应对象变量生成输出的内部例程,如下面所示的代码。
public void WriteOutput(Response respObj)
{
respObj.Write(Request.Form["someField"]);
}
您还应该在 .aspx 源代码中搜索 “<%=”字符串,这也可以用来写输出,如下所示:
<%=myVariable %>
下表列出了一些常见的结合使用输入字段和 Response.Write 的情况。
|
窗体字段 | Response.Write(name.Text); Response.Write(Request.Form["name"]); |
查询字符串 | Response.Write(Request.QueryString["name"]); |
Cookie | 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 属性(如 src、lowsrc、style 和 href)可以与以上的标记结合使用,导致 XSS 攻击。
例如,<img> 标记的 src 属性可能是注入的来源,如下例中所示。
<IMG SRC="javascript:alert('hello');">
<IMG SRC="java
script:alert('hello');">
<IMG SRC="java
script:alert('hello');">
<style> 标记还能通过改变 MIME 类型,成为注入的来源,如下所示。
<style TYPE="text/javascript">
alert('hello');
</style>
检查是否您的代码试图通过筛选掉一些已知的危险字符来净化输入。不要依赖此方法,因为恶意用户一般可找到替代的表示方法绕过验证。相反,您的代码应该对已知安全的输入进行验证。下表列出了表示一些常用字符的若干方式:
|
"(双引号) | " | " | " | \u0022 |
'(单引号) | ' | ' | ' | \u0027 |
&(和号) | & | & | & | \u0026 |
<(小于号) | < | < | < | \u003c |
>(大于号) | > | > | > | \u003e |
处理 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" %>
使用 .NET Framework 1.1 版构建的 Web 应用程序执行输入筛选,以消除潜在的恶意输入,如嵌入脚本。但不要依赖这一点,应该使用它作为纵深防范措施。检查配置文件中的 <pages> 元素,确认 validateRequest 属性是否已经设置为 true。这还可以设置为页级属性。扫描 .aspx 源文件中的 validateRequest,检查它没有为任何页设置为 false。
Internet Explorer 6 SP 1 支持一个新的 HttpOnly cookie 属性,可以防止客户端脚本从 document.cookie 属性访问 cookie。这样返回的将是一个空的字符串。无论何时用户浏览到当前域中的 Web 站点,仍然发送 cookie 到服务器。有关更多信息,请参阅“构建安全的 ASP.NET 页和控件”单元中的“跨站点脚本攻击”部分。
Internet Explorer 6 和更高版本支持 <frame> 和 <iframe> 元素的一个新属性 security。您可以使用 security 属性将用户的受限站点 Internet Explorer 安全区域设置应用于单独的 frame 或者 iframe。有关更多信息,请参阅“构建安全的 ASP.NET 页和控件”单元中的“跨站点脚本攻击”部分。
检查是否使用 innerText 和 innerHTML 属性
如果您创建了一个带有不可信输入的页,应该验证是否使用了 innerText 属性而非 innerHTML。innerText 属性可安全呈现内容并确保不会执行脚本。
托管代码
使用本部分中的审查问题分析整个托管源代码库。这些审查问题无论程序集类型如何都适用。本部分有助于识别常见的托管代码缺陷。有关本部分中所提出问题的更多信息和说明缺陷的代码示例,请参阅“构建安全的程序集”单元。
如果您的托管代码使用了显式代码访问安全功能,请参阅本单元后面“代码访问安全”中的其他审查点。以下审查问题有助于识别托管代码缺陷:
程序集的安全性取决于所包含的类和其他类型。以下问题有助于审查类设计的安全性:
| • | 审查任何标记为 public 的类型或者成员,检查它是否是程序集公共接口的所需部分。 |
| • | 如果您不希望从一个类中派生,使用 sealed 关键字防止代码被潜在的恶意子类滥用。 对于公共基类,您可以使用代码访问安全继承要求来限制可从类中继承的代码。这是一种很好的纵深防范措施。 |
| • | 检查您的类,不要直接公开字段。使用属性公开非私有字段。这使您可验证输入值和应用其他安全检查。 |
| • | 验证是否有效地使用了只读属性。如果字段设计为不能进行设置,应该通过只提供 get 访问器实现只读属性。 |
| • | 这些方法可以从访问类的其他程序集改写。如果并无如此要求的话,使用声明性检查或者删除 virtual 关键字。 |
| • | 如果是这样的话,检查当您完成对象实例时是否调用了 Dispose 方法,以确保所有资源被释放。 |
多线程代码容易出现与时间相关的细微错误,或者出现可能导致安全缺陷的竞争情形。要定位多线程代码,搜索源代码中的文本“Thread”,以识别新的 Thread 对象是在哪里创建的,如以下代码片段中所示:
Thread t = new Thread(new ThreadStart(someObject.SomeThreadStartMethod));
| • | 您的代码如果缓存了安全检查的结果(例如在静态或者全局变量中),然后使用此标志进行后续的安全决策,将非常容易出现竞争情形。 |
| • | 线程创建了新的当前模拟线程吗?新的线程总是使用进程级安全上下文,而不是现有线程的安全上下文。 |
| • | 检查静态类构造函数,检查它们在有两个或者多个线程同时访问时,是否不会出现问题。如果必要,同步线程以防止出现这种情况。 |
| • | 如果对象的 Dispose 方法没有同步,有可能两个线程会执行同一对象的 Dispose。这可带来安全问题,尤其若清理代码释放了非托管资源处理程序(如文件、进程或者线程句柄)更是如此。 |
支持序列化的类要么用 SerializableAttribute 标记,要么派生自 ISerializable。要定位支持序列化的类,请执行对“Serializable”字符串的文本搜索。然后,审查您的代码是否有以下问题:
| • | 如果是这样的话,检查代码,防止通过用 [NonSerialized] 属性标记敏感数据或者实现 ISerializable 然后控制序列化哪个字段,来序列化敏感数据。 如果您的类需要序列化敏感数据,审查如何保护数据。首先考虑加密数据。 |
| • | 如果是这样的话,您的类是否只支持完全信任调用方呢,如由于它安装在具有强名称不包含 AllowPartiallyTrustedCallersAttribute 的程序集中?如果您的类支持部分信任调用方,检查 GetObjectData 方法的实现是否通过使用适当的权限要求授权了呼叫代码。一个较好的技术是使用一个 StrongNameIdentityPermission 要求限制哪些程序集可序列化您的对象。 |
| • | 如果您的代码包括一个接收序列化数据流的方法,应该检查每个字段是否在从数据流中读取时进行了验证。 |
为了辅助定位使用反射的代码,搜索“System.Reflection”— 这是包含反射类型的命名空间。如果您确实使用了反射,审查以下问题以辅助识别潜在的缺陷:
| • | 如果您的代码加载程序集,以创建对象实例并调用类型,它需要从输入数据获取程序集或者类型名称吗?如果是这样的话,检查代码是否使用权限要求进行了保护,确保所有呼叫代码得到授权。例如,使用 StrongNameIdentity 权限要求或者要求完全信任。 |
| • | 如果您的程序集动态生成代码为调用方执行操作,检查调用方是否无法影响生成的代码。例如,您的代码生成依赖调用方提供的输入参数吗?这是应该避免的,如果它非常必要,也应该确保对输入进行验证,而且它不能用来对代码生成产生负面影响。 |
| • | 如果是这样的话,检查是否只有可信代码可调用您的代码。使用代码访问安全权限要求授权呼叫代码。 |
安全的异常处理对于健全的代码而言是必需的,可以确保记录足够的异常详细信息,这样有助于问题的诊断以及防止内部系统详细信息暴露给客户端。审查以下问题,有助于识别潜在的异常处理缺陷:
| • | 检查您的代码是否有过早失败问题,避免进行不必要的消耗资源的处理。如果您的代码确实失败了,检查生成的错误是否不允许用户绕过安全检查运行特权代码。 |
| • | 避免暴露系统或者应用程序详细信息给调用方。例如,不要将调用堆栈返回给最终用户。使用 try/catch 块包装可能生成异常的资源访问或者操作。只处理知道如何处理的异常,避免用一般性的包装包装特定异常。 |
| • | 检查异常详细信息是否在异常来源进行了记录,这样有助于问题诊断。 |
| • | 如果是这样的话,注意在调用堆栈较高处的筛选器的代码可在 finally 块的代码之前运行。检查您是否不依赖 finally 块中的状态变化,因为状态变化将不会在异常筛选器执行之前发生。 |
如果是这样的话,检查您的代码,不要实现自己的加密例程。相反,代码应该使用 System.Security.Cryptography 命名空间或者使用 Win32 加密(如数据保护应用程序编程接口 (DPAPI))。审查以下问题有助于识别潜在的与加密相关的缺陷:
| • | 如果是这样的话,检查在加密的数据需要持久存储较长时间时是否使用了 Rijndael(现在称为高级加密标准 [AES])或者三重数据加密标准(3DES)。较脆弱(但是更快)的 RC2 和 DES 算法只能用来加密生命期较短的数据,如会话数据。 |
| • | 使用所使用的算法可能的最大密钥大小。更大的密钥大小将使对密钥的攻击困难得多,但是会降低性能。 |
| • | 如果是这样的话,检查您是否在需要使用主体证明它知道与您共享的机密时使用了 MD5 和 SHA1。例如,质询-响应身份验证系统使用散列证明客户端知道密码,而无需让客户端传递密码给服务器。使用带有消息身份验证代码 (MAC) 的 HMACSHA1,它要求您和客户端共享密钥。可以提供完整性检查和一定程度的身份验证。 |
| • | 如果是这样的话,检查您的代码是否使用了 System.Security.Cryptography.RNGCryptoServiceProvider 类而不是 Random 类生成随机数。Random 类生成的并不是真正不可重复和预测的随机数。 |
如果您的程序集中需要存储机密,应该审查设计,检查是否绝对必要存储机密。如果必须存储机密,审查以下问题以尽可能安全地存储:
| • | 不要以明文在内存中存储机密较长时间。从存储区中检索机密,将其解密,使用它,然后用零代替存储机密的空间中的值。 |
| • | 是否在 Web.config 或者 Machine.config 中存储明文密码或者 SQL 连接字符串? |
| • | |
| • | 如果是这样的话,检查是否首先加了密然后用受限的 ACL 进行了保护(如果它们存储在HKEY_LOCAL_MACHINE 中)。ACL 并非必需,如果代码使用 HKEY_CURRENT_USER,因为这对于在相关用户帐户下运行的进程是自动受限的。 |
| • | 注 不要依赖混淆工具隐藏机密数据。混淆工具只能使识别机密数据更困难,但是无法解决问题。 |
任何代码都可以将一个方法与一个委托关联起来。这包括以比您的代码还低的信任级运行的潜在恶意的代码。
| • | 如果是这样的话,检查您是否通过使用带 SecurityAction.PermitOnly 的安全权限,限制了委托方法能够获得的代码访问权限。 |
| • | 应该避免这样做,因为您不知道委托代码将会在调用它之前做什么。 |
代码访问安全
所有托管代码都要受到代码访问安全权限要求的限制。许多问题只有在您的代码在部分信任环境中使用时,或者当您的代码或者呼叫代码没有通过代码访问安全策略授予完全信任时,才会变得显而易见。
使用以下审查点检查您使用代码访问安全是否合适和安全:
| • | |
| • | |
| • | |
| • | |
| • | |
| • | |
| • | 使用 Deny 或者 PermitOnly 了吗? |
| • | |
| • | |
如果您的代码支持部分信任调用方,它受到攻击的可能性要大得多,因此执行全面和彻底的代码审查尤其重要。审查 Web 应用程序中的 <trust> 级配置设置,看是否运行在部分信任级。如果确实如此,为应用程序开发的程序集需要支持部分信任调用方。
| • | 如果确实如此,则默认安全策略可确保它无法被部分信任的调用方调用。公共语言运行库 (CLR) 会发出完全信任的隐式链接要求。如果您的程序集不具有强名称,它可以被任何代码调用,除非采用明确的步骤(例如通过显式要求完全信任)限制调用方。 注 ASP.NET 应用程序调用的具有强名称的程序集必须安装在全局程序集缓存中。 |
| • | 如果具有强名称的程序集包含 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 类库的代码受权限要求的限制。您的代码不需要发出同样的要求。这将导致重复和浪费的堆栈审核。 |
链接要求与常规要求不同,只检查直接调用方。它们不执行完整的堆栈审核,因此使用链接要求的代码容易受到引诱攻击。有关引诱攻击的信息,请参阅“代码访问安全实践”单元中的“链接要求”。
搜索代码中的“.LinkDemand”字符串以识别使用链接要求的地方。它们只能声明性地使用。以下代码片段举出了一个例子:
[StrongNameIdentityPermission(SecurityAction.LinkDemand,
PublicKey="00240000048...97e85d098615")]
public static void SomeOperation() {}
有关本部分中所提问题的更多信息,请参阅“代码访问安全实践”单元中的“链接要求”。以下问题有助于审查代码中链接要求的使用:
| • | 一种防范方法是尽可能避免使用链接要求。不要仅仅因为要提高性能和消除完整的堆栈审核而使用它们。与其他 Web 应用程序的性能问题如网络延迟和数据库访问相比,堆栈审核的代价很小。链接要求只有在您知道而且可限制哪些代码可调用您的代码时才是安全的。 |
| • | 当您使用链接要求时,需要依赖调用方防止引诱攻击。链接要求只有在知道而且可准确限制代码的直接调用方时才是安全的,您可以信任这些调用方授权它们的调用方。 |
| • | 如果是这样的话,您的代码是否通过要求代码调用方的安全权限提供授权呢?传给您的方法的参数可传递到调用的代码吗?如果是这样的话,它们能够恶意影响调用的代码吗? |
| • | 当您在方法中添加链接要求时,它将改写类的链接要求。检查方法是否也包含了类级链接要求。 |
| • | 链接要求不能被派生类型继承,也不能在调用派生类型的改写方法时使用。如果您改写一个需要用链接要求保护的方法,对改写方法应用链接要求。 |
| • | 链接要求并不能防止不可信调用方构造结构。这是因为默认构造函数不会为结构自动生成,所以结构级链接要求只适用于使用显式构造函数的时候。 |
| • | 搜索 Interface 关键字来查找显式接口。如果使用了显式接口,应该检查是否使用链接要求标记了方法实现。如果使用了,还需检查接口定义是否包含同样的链接要求。否则,调用方可能绕过链接要求。 |
检查以下权限类型是否只授予高度可信代码。它们大部分都不拥有自己的专用权限类型,而是使用一般性的 SecurityPermission 类型。您应该严格检查使用这些类型的代码,确保可将风险降低至最小。同样,您必须有很充分的理由才能使用这些权限。
|
SecurityPermission.UnmanagedCode | 代码可调用非托管代码。 |
SecurityPermission.SkipVerification | 程序集中的代码不再需要验证类型安全性。 |
SecurityPermission.ControlEvidence | 代码可提供其自己的证据供安全策略评估使用。 |
SecurityPermission.ControlPolicy | 代码可查看和更改策略。 |
SecurityPermission.SerializationFormatter | 代码可使用序列化。 |
SecurityPermission.ControlPrincipal | 代码可操作用于授权的主体对象。 |
ReflectionPermission.MemberAccess | 代码可通过反射调用一个类型的私有成员。 |
SecurityPermission.ControlAppDomain | 代码可创建新的应用程序域。 |
SecurityPermission.ControlDomainPolicy | 代码可改变域策略。 |
使用 Visual Studio .NET 检查项目属性,查看是否 Allow Unsafe Code Blocks 设置为 true。这将设置 /unsafe 编译器标志,它告知编译器代码包含不安全的块,并请求程序集具有最低的 SkipVerification 权限。如果您用 /unsafe 选项编译,应该审查为什么需要如此。如果理由合理,要格外小心地审查源代码中潜在的缺陷。
ASP.NET 页和控件
| • | |
| • | |
| • | |
| • | |
| • | |
| • | |
| • | |
| • | |
| • | 您的 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"/>
攻击者可通过公布的窗体字段向 Web 页和控件传递恶意输入。应该检查是否验证了所有窗体字段输入,包括隐藏的窗体字段。验证它们的类型、范围、格式和长度。使用以下问题审查 ASP.NET 输入的处理:
| • | 通常您应该避免这样做,因为这是一种高风险的操作。为什么需要用户指定文件名或者路径而不是让应用程序根据用户标识选择位置呢? 如果您接受文件名和路径作为输入,代码将非常容易出现规范化错误。如果您必须从用户接受路径输入,则应该检查是否验证了路径安全性和规范化。检查代码是否使用了 System.IO.Path.GetFullPath。 |
| • | 如果您通过用户提供的文件名调用 MapPath,应该检查您的代码是否使用了接受一个 bool 参数的 HttpRequest.MapPath 改写,这可防止跨应用程序的映射。 try
{
string mappedPath = Request.MapPath( inputPath.Text,
Request.ApplicationPath, false);
}
catch (HttpException)
{
// Cross application mapping attempted.
}
|
| • | 检查您的代码是否验证了从公开的窗体字段接收的数据和其他形式的 Web 输入(如查询字符串)的数据类型。对于非字符串数据,应该检查代码是否通过使用 .NET Framework 的类型系统执行了类型检查。您可以将字符串输入转换为一个强类型的对象,并捕获任何类型转换异常。例如,如果一个字段包含日期,应该使用它构造一个 System.DateTime 对象。如果它包含以年表示的年龄,通过使用 Int32.Parse 将其转换为 System.Int32 对象,并捕获格式异常。 |
| • | 检查输入字符串是否通过使用正则表达式进行了长度验证、可接受的字符集和模式验证。您可以使用一个 RegularExpressionValidator 验证控件或者直接使用 RegEx 类。不要搜索无效数据;只搜索已知为正确的信息格式。 |
| •< |