安全代码审查的目的是要识别出会导致安全问题和事故的不安全编码技术和漏洞。虽然可能很耗时,但代码审查必须是项目开发周期中的常规事件,这是因为在开发时修复安全缺陷会比以后在产品部署或维护修复周期中再做这项工作节省大量的成本和工作量。
本模块帮助您审查使用Microsoft.NETFramework建立的托管ASP.NETWeb应用程序代码。本模块按功能区进行组织,并通过对所需审查的问题列出完整的列表,为您的代码审查过程提供方法指导和框架。
启动审查过程的好方法是通过FxCop分析工具运行已编译的程序集。此工具分析二进制程序集(而非源代码),以确保它们符合MSDN中可用的.NETFramework设计指南。它还检查您的程序集是否具有强大的名称,以提供防篡改和其他安全收益。此工具随规则的预定义集合提供,虽然您可以自定义和扩展它们。
要帮助执行审查过程,请检查您是否熟悉可用来找到文件中的字符串的文本搜索工具。此种工具使您可以快速找到易受攻击的代码。本模块后面提供的许多审查问题都指出当查找特定漏洞时要搜索的最佳字符串。
您可能已有喜欢的搜索工具。如果没有,可以使用VisualStudio.NET中的“在文件中查找”工具或随MicrosoftWindows操作系统提供的Findstr命令行工具。
执行源代码的详细逐行分析之前,首先快速搜索整个代码库,以便识别硬编码密码、帐户名和数据库连接字符串。扫描代码,搜索诸如下列的常用字符串模式:“key”、“secret”、“password”、“pwd”和“connectionstring”。例如,要在应用程序的Web目录中搜索字符串“password”,请按如下从命令提示使用Findstr工具:
findstr/N/G:SearchStrings.txt*.aspx/N在找到匹配项时打印相应的行号。/G指出包含搜索字符串的文件。在此例中,将在所有ASP.NET页(*.aspx)中搜索SearchStrings.txt中所包含的字符串。
还可以将Findstr命令与ildasm.exe工具一起使用,以便搜索硬编码字符串的二进制程序集。下面的命令使用ildasm.exe来搜索用来找出字符串常量的ldstr中间语言语句。注意下面显示的输出如何显示硬编码数据库连接和众所周知的sa帐户的密码。
Ildasm.exesecureapp.dll/text|findstrldstrIL_000c:ldstr"RegisterUser"IL_0027:ldstr"@userName"IL_0046:ldstr"@passwordHash"IL_0065:ldstr"@salt"IL_008b:ldstr"Exceptionaddingaccount."IL_000e:ldstr"LookupUser"IL_0027:ldstr"@userName"IL_007d:ldstr"SHA1"IL_0097:ldstr"Execeptionverifyingpassword."IL_0009:ldstr"SHA1"IL_003e:ldstr"Logonsuccessful:Userisauthenticated"IL_0050:ldstr"Invalidusernameorpassword"IL_0001:ldstr"Server=AppServer;database=users;username='sa'password=password"注意Ildasm.exe位于\ProgramFiles\MicrosoftVisualStudio.NET2003\SDK\v1.1\bin文件夹中。有关受支持的命令行参数的详细信息,请运行ildasm.exe/。
无论何时代码在返回到客户端的输出HTML流中使用输入参数时,它易受到跨站点脚本(XSS,也称为CSS)攻击。即使在进行代码审查之前,也可以运行简单测试,以检查应用程序是否易受XSS攻击。搜索用户输入信息发送回浏览器的页面。
"onmouseover=alert('hello');"开发人员使用的常用技术是筛选“<”和“>”。如果您检查的代码筛选这些字符,则转而使用下面的代码进行测试:
">搜索“.Write”在.aspx源代码和为您的应用程序开发的任何其他程序集包含的代码中,搜索“.Write”字符串。它定位了Response.Write的出现以及任何可以通过响应对象变量(例如下面所示的代码)生成输出的内部例程。
publicvoidWriteOutput(ResponserespObj){respObj.Write(Request.Form["someField"]);}您还应该在.aspx源代码中搜索“<%=”字符串,它也可以用来写输出,如下所示:
SqlDataReaderreader=cmd.ExecuteReader();Response.Write(reader.GetString(1));识别具有潜在危险性的HTML标记和属性下列常用HTML标记(并不全面)会允许恶意用户注入脚本代码:
例如, 标记的src属性可能是注入源,如下例所示。
按如下所示更改MIME类型后,检查并确定您的代码是否通过筛选掉某些已知的危险字符来净化输入。不要依赖此方法,这是因为恶意用户通常会找到另一表示来绕过您的验证。相反,代码应该对已知安全的输入进行验证。下表显示了表示一些常用字符的各种方法:
表21.2:字符表示
"(双引号)
"
"
\u0022'(单引号)
'
'
\u0027&(表示and的符号)
&
\u0026<(小于)
<
\u003c>(大于)
>
\u003e识别处理URL的代码处理URL的代码可能易受攻击。检查代码,确定它是否易受下列常见攻击:
如果Web服务器没有通过安装最新安全修补程序来保持最新,则它可能易受目录遍历攻击和双斜杠攻击,例如:
要帮助防止攻击者使用规范化和多字节转义序列来欺骗您的输入验证例程,请检查字符编码是否设置正确,以限制可以表示输入的方法。
检查应用程序Web.config文件是否已经对如下所示的元素所配置的requestEncoding和responseEncoding属性进行了设置。
也可以使用 标记或如下所示的ResponseEncoding页面级别属性在页面级别设置字符编码。
<%@PageResponseEncoding="ISO-8859-1"%>有关详细信息,请参阅模块10构建安全的ASP.NET页面和控件。
使用.NETFramework版本1.1内置的Web应用程序执行输入筛选,以便消除潜在恶意输入(例如嵌入的脚本)。不要依赖于此,但是可以使用它进行深度防御。检查配置文件中的元素,以确认validateRequest属性是否设置为“True”。它还可以设置为页面级别属性。扫描.aspx源文件,以查找validateRequest,检查它没有对任何页面设置为“False”。
InternetExplorer6SP1支持新的HttpOnlyCookie属性,此属性防止客户端方的脚本访问document.cookie属性中的Cookie。相反,返回空字符串。当用户浏览到当前域中的网站时,Cookie仍发送到服务器。有关详细信息,请参阅模块10构建安全的ASP.NET页面和控件中的“跨站点脚本”部分。
InternetExplorer6及更高版本支持 和
如果使用不受信任的输入创建页面,请验证您使用的是innerText属性,而不是innerHTML。innerText属性将内容呈现为安全,并确保不执行脚本。
当代码使用输入参数构建SQL语句时,它易受到SQL注入攻击。与XSS问题一样,导致SQL注入攻击的原因是对用户输入给予太多信任,以及没有验证输入及其格式是否正确。
下面的过程帮助您找到SQL注入漏洞:
1.
查找访问数据库的代码。扫描字符串“SqlCommand”、“OleDbCommand”或“OdbcCommand”。
2.
检查代码是否使用参数化存储过程。存储过程不能单独防止SQL注入攻击。检查代码是否使用参数化存储过程。检查代码是否使用键入的参数对象,例如SqlParameter、OleDbParameter或OdbcParameter。下面的示例显示了SqlParameter的使用:
SqlDataAdaptermyCommand=newSqlDataAdapter("spLogin",conn);myCommand.SelectCommand.CommandType=CommandType.StoredProcedure;SqlParameterparm=myCommand.SelectCommand.Parameters.Add("@userName",SqlDbType.VarChar,12);parm.Value=txtUid.Text;键入的SQL参数检查输入的类型和长度,并确保userName输入值被视为文本值,而不是被视为数据库中的可执行代码。
3.
检查代码是否使用SQL语句中的参数。如果不使用存储过程,请检查代码是否使用它构建的SQL语句中的参数,如下例所示:
selectstatusfromUserswhereUserName=@userName检查下面的方法是否未使用,其中输入直接用于构建使用字符串串联的可执行SQL语句:
stringsql="selectstatusfromUserswhereUserName='"+txtUserName.Text+"'";4.
检查代码是否尝试筛选输入。常用方法是开发筛选器例程,以便将转义符添加到对SQL具有特殊意义的字符。这是不安全的方法,由于字符表示问题,您不应该依赖它。
当您审查缓冲区溢出的代码时,请将审查重点放在通过P/Invoke或COMinterop层调用非托管代码的代码上。由于当访问数组时自动检查数组界限,托管代码本身受缓冲区溢出危害要小得多。只要调用Win32DLL或COM对象,就应该严格检查API调用。
下面的过程帮助您找到缓冲区溢出漏洞:
查找对非托管代码的调用。扫描您的源文件,以查找“System.Runtime.InteropServices”,它是您调用非托管代码时使用的名称空间名。
voidSomeFunction(char*pszInput){charszBuffer[10];//注意,没有长度检查。输入直接复制到缓冲区//应该检查长度或使用strncpy。strcpy(szBuffer,pszInput);...}注意如果使用strncpy,缓冲区溢出仍会发生,这是因为它不检查目标字符串中的多余空格,它只限制复制的字符串的数量。
如果由于不拥有非托管代码而不能检查它,则可以通过故意传递长输入字符串和无效参数来严格测试API。
检查文件路径长度。如果托管API接受文件名和路径,请检查您的包装方法是否检查文件名和路径不超过260个字符。这是由Win32MAX_PATH常量定义的。另请注意,目录名称和注册表项最多可以为248个字符。
4.
检查输出字符串。检查代码是否使用StringBuilder接收从非托管API传回的字符串。检查StringBuilder的长度是否足以保留非托管API可以退还的最长字符串,这是因为从非托管代码传回的字符串可能为任意长度。
5.
检查数组界限。如果使用数组将输入传递给非托管API,请检查托管包装是否验证未超出数组容量。
6.
检查非托管代码是否是使用/GS开关编译的。如果您拥有非托管代码,请使用/GS开关启用堆栈探查,以检测某些类型的缓冲区溢出。
使用本部分中的审查问题对整个托管源代码库进行分析。无论程序集的类型如何,这些审查问题都适用。本部分帮助您识别常见托管代码漏洞。有关本部分中产生的问题以及说明漏洞的代码示例的详细信息,请参阅模块7构建安全的程序集。
您的类设计安全吗?
您是否创建了线程?
您是否使用了序列化?
您是否使用了反射?
您是否处理了例外?
您是否使用了加密?
您是否存储了机密?
您是否使用了委托?
程序集只与它包含的类和其他类型一样安全。下列问题可以帮助您审查类设计的安全性:
您是否限制了类型和成员可见性?审查任何标记为“公用”的类型或成员,并检查它是否是程序集的公用接口的一个想要的部分。
非基本类是否已封装?如果不希望类派生,请使用已封装的关键字来避免潜在恶意子类误用代码。
对于公用基本类,可以使用代码访问安全继承需求来限制可以从类继承的代码。这是一个好的深度防御方法。
您是否使用了属性来公开字段?检查您的类没有直接公开字段。使用属性来公开非专用字段。这使您可以验证输入值和应用其他安全检查。
您是否使用了只读属性?验证您已经有效使用了只读属性。如果字段未设计为设置,请通过只提供get访问器来实现只读属性。
您是否实现IDisposable?如果是,请检查当您以对象实例结束时调用了Dispose方法,以确保释放所有资源。
Threadt=newThread(newThreadStart(someObject.SomeThreadStartMethod));下列审查问题可以帮助您识别潜在线程漏洞:
您的代码是否缓存了安全检查的结果?如果您的代码缓存安全检查的结果(例如在静态或全局变量中),然后使用标志进行后续安全决策,则特别易受攻击。
您的代码是否进行了模拟?创建新线程的线程当前是否正在模拟?新线程始终假设进程级别的安全上下文,而非现有线程的安全上下文。
您的代码是否包含静态类构造函数?检查静态类构造函数,以检查当两个或更多线程同时访问它们时它们是否不易受攻击。如有必要,将线程同步,以避免此情况。
您是否同步了Dispose方法?如果未同步某一对象的Dispose方法,则很可能两个线程在同一个对象上执行Dispose。这会产生安全问题,尤其是当清理代码释放非托管资源处理程序(例如文件、进程或线程句柄)时。
支持序列化的类标记了SerializableAttribute,或从ISerializable派生。要找到支持序列化的类,请对“可序列化”字符串执行文本搜索。然后审查代码中是否有下列问题:
类是否包含敏感数据?如果是,请检查代码是否通过此方法防止敏感数据被序列化:使用或通过实现ISerializable对具有[NonSerialized]属性的数据进行标记,然后控制序列化哪些字段。
如果类需要将敏感数据序列化,请检查保护数据的方法。首先考虑对数据加密。
类是否验证了数据流?如果代码包括接收序列化数据流的方法,请检查在从数据流读取每个字段时是否对每个字段进行了验证。
要帮助找到使用反射的代码,请搜索“System.Reflection”—这是包含反射类型的名称空间。如果确实使用了反射,请审查下列问题,以帮助识别潜在漏洞:
您是否在运行时动态创建了代码?如果程序集动态生成代码以便为调用方执行操作,请检查调用方是否不会影响生成的代码。例如,代码生成是否依赖调用方提供的输入参数?这应该是可以避免的,或者如果一定需要,请确保输入经过验证且它不会用来反面影响代码生成。
强大的代码需要安全例外处理,以确保记录了足够的例外详细信息,以便帮助进行问题诊断和帮助防止内部系统详细信息暴露给客户端。审查下列问题,以帮助识别潜在例外处理漏洞:
您以前是否曾失败?检查代码以前是否曾失败,以避免消耗资源的不必要处理。如果代码确实失败过,请检查导致的错误是否不允许用户绕过安全检查来运行特权代码。
您如何处理例外?避免将系统或应用程序详细信息暴露给调用方。例如,不要将调用堆栈返回给最终用户。对可以生成带有try/catch块的例外的资源访问或操作进行包装。只处理您知道如何处理的例外,避免使用一般包装来包装特定例外。
您是否记录了例外详细信息?检查是否在例外源处记录了例外详细信息以帮助进行问题诊断。
您是否使用了例外筛选器?如果是,一定注意调用堆栈中较高位置的筛选器中的代码可以在最后块中的代码之前运行。检查您不依赖最后块中的状态更改,这是因为在例外筛选器执行之前,状态更改不会发生。
有关例外筛选器漏洞的示例,请参阅模块7构建安全的程序集中的“例外管理”。
您是否使用了最大可能大小的密钥?请为您正在使用的算法使用最大可能大小的密钥。较大密钥大小使针对密钥的攻击变得更困难,但降低了性能。
您是否使用了哈希?如果是,请检查当需要规则来证明它知道与您共享的机密时使用了MD5和SHA1。例如,质询响应身份验证系统使用哈希来验证客户端知道密码,而无需客户端将密码传递给服务器。将HMACSHA1与消息验证代码(MAC)一起使用,需要您和客户端共享密钥。这可以提供完整性检查和某种程度的身份验证。
您是否出于加密目的生成随机编号?如果是,请检查代码使用System.Security.Cryptography.RNGCryptoServiceProvider类(而不是使用Random类)生成了随机编号。Random类不生成不可重复或不可预测的真正随机编号。
如果程序集存储了机密,请检查设计,以检查绝对需要存储机密。如果必须存储机密,请审查下列问题,以便尽可能安全地进行此操作:
注意不要依赖迷惑工具来隐藏机密数据。迷惑工具使识别机密数据更困难,但不能解决问题。
您是否在调用委派之前使用了断言?应避免这一点,这是因为您不知道委派代码在调用之前将如何进行操作。
所有托管代码都服从代码访问安全权限需求。只有当在部分信任环境中使用代码或当代码访问安全策略未向调用代码授予完全信任时,许多问题才是明显的。
有关本部分中产生的问题的详细信息,请参阅模块8代码访问安全的实践。
使用下列审查点检查您是否适当且安全地使用了代码访问安全:
您是否支持部分信任调用方?
您是否限制了对公用类型和成员的访问?
您是否调用了断言?
您是否在应该的时候使用了权限要求?
您是否使用了链接请求?
您是否使用了Deny或PermitOnly?
您是否使用了部分危险的权限?
您是否使用/unsafe选项进行了编译?
如果代码支持部分信任调用方,它甚至更有可能受到攻击,因此执行广泛且彻底的代码审查尤其重要。审查Web应用程序中的级别配置设置,以确定它是否在部分信任级别运行。如果是,则您为应用程序开发的程序集需要支持部分信任调用方。
下列问题帮助您识别潜在易受攻击区域:
程序集是否采用了强命名?如果是,则默认安全策略确保它不能被部分信任调用方调用。公共语言运行库(CLR)发出对完全信任的明确链接要求。如果程序集没有进行采用强命名,则任何代码都可以调用它,除非您采取明确步骤限制调用方,例如通过明确要求完全信任。
注意ASP.NET应用程序调用的强命名程序集必须安装在全局程序集缓存中。
您是否提供了对象参考?检查方法返回和ref参数,以确定您的代码返回对象参考的位置。检查部分信任代码未提供从需要完全信任调用方的程序集获取的对象的参考。
可以使用代码访问安全识别请求来限制对公用类型和成员的访问。这是减少程序集的攻击面的有效方法。
您是否使用身份请求来限制调用方?如果您拥有只希望特定程序集在特定应用程序中使用的类或结构,则可以使用身份请求来限制调用方的范围。例如,可以使用带有StrongNameIdentityPermission的请求将调用方限制在一组特定程序集(此程序集使用与请求中的公钥对应的私钥进行签名)。
您是否使用了继承请求来限制子类?如果知道只有特定代码应该从基本类继承,请检查类是否使用了具有StrongNameIdentityPermission的继承请求。
您是否请求了最低权限?搜索“.RequestMinimum”字符串,以确定代码是否使用权限请求来指定其最低权限需求。您应该执行此操作,以清楚地说明程序集的权限需求。
您是否请求了可选权限或拒绝权限?请搜索“.RequestOptional”和“.RequestRefuse”字符串。如果使用这两个操作之一来开发最低特权代码,请注意您的代码不再可以调用强命名程序集,除非它们使用AllowPartiallyTrustedCallersAttribute进行了标记。
您是否将类和成员级别特性混合在一起?请不要这样做。成员特性(例如方法或属性中的成员特性)使用相同的安全操作来替换类级别特性,不要将它们混合在一起。
扫描代码,以获取Assert调用。这可以找到Debug.Assert的实例。在CodeAccessPermission对象中查找您的代码调用Assert的位置。当您断言代码访问权限时,将代码访问安全权限请求堆栈行走短路,这是危险的操作。您的代码采取哪些步骤确保恶意调用方不会利用断言来访问安全的资源或特权操作?审查下列问题:
您是否将Assert调用与RevertAssert相匹配?检查对Assert的每个调用是否与对RevertAssert的调用相匹配。当调用Assert的方法返回时,隐含删除Assert,但是最佳操作是在Assert调用之后尽可能快地明确调用RevertAssert。
您是否公开了自定义资源或特权操作?如果代码通过非托管代码公开自定义资源或特权操作,请检查它是否发出了适当的权限请求,根据资源的性质,此权限请求可能是内置权限类型或自定义权限类型。
您是否发出了冗余请求?使用.NETFramework类库的代码服从权限请求。您的代码不需要发出相同请求。这会导致重复和浪费的堆栈行走。
与常规请求不同,链接请求只检查立即调用方。它们不执行完全堆栈行走,因此,使用链接请求的代码易受到引诱攻击。有关引诱攻击的信息,请参阅模块8代码访问安全的实践中的“链接请求”。
[StrongNameIdentityPermission(SecurityAction.LinkDemand,PublicKey="00240000048...97e85d098615")]publicstaticvoidSomeOperation(){}有关本部分中产生的问题的详细信息,请参阅模块8代码访问安全的实践中的“链接请求”。下列问题帮助您审查代码中链接请求的使用:
为什么使用链接请求?防御方法是尽可能避免链接请求。不要只使用它们来提高性能和消除完全堆栈行走。与其他Web应用程序性能问题(例如网络滞后和数据库访问)的成本相比,堆栈行走的成本不高。只有当您了解并可以限制哪一个代码可调用您的代码时,链接请求才是安全的。
您在方法和类级别是否使用了链接请求?当您将链接请求添加到方法时,它改写类中的链接请求。检查此方法是否还包括类级别链接请求。
您是否使用了未封装的类中的链接请求?派生类型不继承链接请求,且在派生类型中调用已改写的方法时,不使用链接请求。如果改写需要使用链接请求保护的方法,请将链接请求应用于已改写的方法。
您是否使用了链接请求以保护结构?链接请求不防止不受信任的调用方构建结构。这是因为不为结构自动生成默认构造函数,因此只有当使用明确构造函数时才应用结构级别链接请求。
您是否使用了明确接口?搜索Interface关键字以识别接口。如果这样做,请检查方法实现是否使用链接请求进行了标记。如果进行了标记,请检查接口定义是否包含相同的链接请求。否则,调用方可能绕过链接请求。
表21.3:危险的权限
SecurityPermission.UnmanagedCode
代码可以调用非托管代码。
SecurityPermission.SkipVerification
程序集内的代码不再必须验证为安全类型。
SecurityPermission.ControlEvidence
代码可以提供其自己的证据,供安全策略评估使用。
SecurityPermission.ControlPolicy
代码可以查看和更改策略。
SecurityPermission.SerializationFormatter
代码可以使用序列化。
SecurityPermission.ControlPrincipal
ReflectionPermission.MemberAccess
代码可以通过反射调用某一类型的私有成员。
SecurityPermission.ControlAppDomain
代码可以创建新的应用程序域。
SecurityPermission.ControlDomainPolicy
代码可以更改域策略。
使用VisualStudio.NET检查项目属性,以确定“允许不安全代码块”是否设置为“True”。这可以设置/unsafe编译器标志,它告诉编译器代码包含不安全的块,并请求在程序集内放置最低SkipVerification权限。
如果使用/unsafe编译,请检查需要这样做的原因。如果原因合法,请特别小心检查潜在漏洞的源代码。
通常,不应该将非托管代码直接公开给部分受信任调用方。有关本部分中产生的问题的详细信息,请参阅模块7构建安全的程序集和模块8代码访问安全的实践中的“非托管代码”部分。
使用下列检查问题验证非托管代码的使用:
您是否断言了非托管代码权限?
//请求自定义EncryptionPermission。(newEncryptionPermission(EncryptionPermissionFlag.Encrypt,storeFlag)).Demand();//断言非托管代码权限。(newSecurityPermission(SecurityPermissionFlag.UnmanagedCode)).Assert();//现在使用P/Invoke调用非托管DPAPI函数。有关详细信息,请参阅模块8代码访问安全的实践中的“Assert和RevertAssert”。
注意添加SupressUnmanagedCodeSecurityAttribute将interop层所发出的对UnmanagedCode权限的明确请求返回到LinkDemand。您的代码易受到引诱攻击。
非托管入口点是否公开可见?请检查非托管代码入口点是否标记为“专用”或“内部”。应该强制调用方调用对非托管代码进行封装的托管包装方法。
注意所有适用于C和C++的代码审查规则和规定都适用于非托管代码。
您是否对枚举类型进行了范围检查?请在将枚举值传递给本机方法之前验证所有枚举值是否在范围中。
您是否对非托管代码方法使用了命名惯例?所有非托管代码应该在具有下列名称的包装类中:NativeMethods、UnsafeNativeMethods和SafeNativeMethods。您必须全面审查UnsafeNativeMethods中的所有代码以及传递给本机API的参数,以查找安全漏洞。
您是否调用了潜在危险的API?您应该能够调整所有Win32API调用的使用。危险的API包括:
切换安全上下文的线程函数
访问令牌函数,它可以更改或公开有关安全令牌的信息
凭据管理函数,包括创建令牌的函数
可以解密和访问私钥的加密API函数
可以读写内存的内存管理函数
可以访问系统机密的LSA函数
使用此部分中的审查问题来审查ASP.NET页面和控件。有关本部分中产生的问题的详细信息,请参阅模块10构建安全的ASP.NET页面和控件。
您是否禁用了详细错误消息?
您是否禁用了跟踪?
您是否验证了表格字段输入?
您是否易受到XSS攻击?
您是否验证了查询字符串和Cookie输入?
您是否依赖HTTP头来确保安全?
您是否确保了视图状态的安全?
您是否防止了XSS?
您的global.asax事件处理程序是否安全?
您是否提供了适当的权限?
如果让例外传播到应用程序边界之外,则ASP.NET会将详细信息返回给调用方。这包括完全堆栈跟踪和其他对攻击者有用的信息。检查元素,确保模式属性设置为“On”或“RemoteOnly”。
您是否禁用了跟踪?跟踪信息对攻击者也特别有用。检查元素以确保禁用跟踪。
您是否验证了表格字段输入?攻击者可以通过邮寄表格字段将恶意输入传递到您的网页和控件。检查您是否对所有表格字段输入(包括隐藏表格字段)进行了验证。验证它们的类型、范围、格式和长度。使用下列问题审查ASP.NET输入处理:
输入是否包括文件名或文件路径?通常您应该避免此操作,因为它是高风险操作。您为什么需要用户指定文件名或路径,而不是指定根据用户身份选择位置的应用程序?
如果接受文件名和路径作为输入,则代码易产生规范化缺陷。如果必须接受来自用户的路径输入,则检查它是否验证为安全路径和是否规范化。检查代码是否使用System.IO.Path.GetFullPath。
您是否调用了MapPath?如果使用用户提供的文件名调用MapPath,请检查您的代码是否使用了接受bool参数(此参数防止交叉应用程序映射)的HttpRequest.MapPath的替代。
try{stringmappedPath=Request.MapPath(inputPath.Text,Request.ApplicationPath,false);}catch(HttpException){//尝试了交叉应用程序映射。}有关详细信息,请参阅模块10构建安全的ASP.NET页面和控件中的“使用MapPath”部分。
您如何验证数据类型?检查您的代码是否验证了从邮寄表格字段以及其他Web输入(如查询字符串)表格接收的数据的数据类型。对于非字符串数据,请检查代码是否使用.NETFramework类型系统执行类型检查。可以将字符串输入转换为强键入对象,并捕获任何类型转换例外。例如,如果字段包含日期,则使用它构建System.DateTime对象。如果它包含以年份表示的年龄,则将它转换为使用Int32.Parse的System.Int32对象,并捕获格式例外。
您如何验证字符串类型?检查是否使用正则表达式对长度以及可接受字符和模式集验证了输入字符串。可以使用RegularExpressionValidator验证控件或直接使用RegEx类。不要搜索无效数据,只搜索您已知正确的信息格式。
您是否使用了验证控件?如果使用验证控件(例如RegularExpressionValidator、RequiredFieldValidator、CompareValidator、RangeValidator或CustomValidator),请检查您是否未禁用服务器方验证和不纯粹依赖客户端方验证。
您是否依赖客户端方验证?请不要这样做。只使用客户端方验证来提高用户经验。检查是否在服务器验证了所有输入。
检查您的代码是否对URL查询字符串和从Cookie提取的输入字段所传递的输入字段进行了验证。要找到易受攻击的代码,请搜索下列文本字符串:
“Request.QueryString”
“Request.Cookies”
检查是否像对表格字段那样(请参阅上一部分“您是否验证了表格字段输入?”)使用键入的对象及正则表达式对输入的类型、范围、格式和长度进行了验证。另外,将HTML或URL编码考虑为从用户输入派生的任何输出,因为这将否定任何可导致XSS缺陷的无效构建。
如果应用程序使用视图状态,它是否防篡改?审查下列问题:
是否在应用程序级别启用了视图状态保护?检查应用程序Machine.config或Web.config文件中的元素的enableViewState属性,以确定是否在应用程序级别启用了视图状态。然后,检查enableViewStateMac是否设置为“True”,以确保它防篡改。
您是否改写了代码中的视图状态保护?检查通过将Page.EnableViewStateMac属性设置为“False”使您的代码未禁用视图状态。只有当页面不使用视图状态时,这才是安全的设置。
global.asax文件包含由ASP.NET和HTTP模块生成的应用程序级别事件的事件处理代码。审查下列事件处理程序,以确保代码不包含漏洞:
Application_Start。这里放置的代码在ASP.NET进程帐户(而不是模仿的用户)的安全上下文下运行。
Application_BeginRequest。这里放置的代码在ASP.NET进程帐户或者模仿的用户的安全上下文下运行。
Application_EndRequest。如果需要修改传出的Cookie的属性,例如设置“安全”位或域,则Application_EndRequest是执行此操作的正确位置。
Application_AuthenticateRequest。它执行用户身份验证。
Application_Error。调用此事件处理程序时的安全上下文可以对写入Windows事件日志产生影响。安全上下文可以是进程帐户或模拟的帐户。
受保护的voidSession_End。此事件的启动没有确定性,且只对进程中会话状态模式启动此事件。
您是否已经配置了元素以便指定哪些用户和用户组可以访问特定页面?
您如何保护对页面类的访问?您是否使用了对您的类的增加主要权限需求来确定哪些用户和用户组可以访问类?
Server.Transfer使用另一模块处理页面,而不是从服务器产生强制权限的另一请求。如果考虑目标网页的安全,请不要使用Server.Transfer。相反,使用HttpResponse.Redirect。
ASP.NETWeb服务共享许多与ASP.NETWeb应用程序相同的功能。在说明下列特定于Web服务的问题之前,请针对ASP.NET页面和控件部分中的问题审查您的Web服务。有关本部分中产生的问题的详细信息,请参阅模块12构建安全的WebServices。
您是否公开了受限操作或数据?
您是否限制了特权操作?
您是否使用了自定义身份验证?
您是否验证了所有输入?
您是否验证了SOAP头?
如果Web服务公开受限操作或数据,请检查此服务是否对调用方进行身份验证。可以使用平台身份验证机制(例如NTLM、Kerberos、基本身份验证或客户端X.509证书),也可以在SOAP头中传递身份验证令牌。
如果传递身份验证令牌,可以使用WebServiceEnhancements(WSE),以便以符合正在产生的WS安全标准的方式使用SOAP头。
代码访问安全策略的信任级别确定Web服务可以访问的资源类型。在Machine.config或Web.config中检查元素配置。
请使用WebServiceEnhancements(WSE)提供的功能,而不要创建自己的身份验证方案。
如果在应用程序中使用自定义SOAP头,请检查信息是否未被篡改或重放。对头信息进行数字签名,以确保它未被篡改。可以使用WSE帮助以标准方法对Web服务消息进行签名。
检查SoapException和SoapHeaderException对象是否用来得体地处理错误和向客户端提供最低必需信息。验证是否为解决问题而相应记录了例外。
本部分列出您在审查企业服务应用程序中使用的接受服务的组件时应该考虑的关键审查点。有关本部分中产生的问题的详细信息,请参阅模块11构建安全服务型组件。
您是否使用了程序集级别元数据?
您是否防止了匿名访问?
您是否使用了受限模拟级别?
您是否使用了基于角色的安全?
您是否使用了对象构造函数字符串?
您是否在中间层中进行了审核?
[assembly:ApplicationAccessControl(Authentication=AuthenticationOption.Call)]您是否使用了受限模拟级别?您为接受服务的组件定义的模拟级别确定了您与之通信的任何远程服务器的模拟能力。搜索“ImpersonationLevel”字符串,以检查您的代码是否设置了级别。
[assembly:ApplicationAccessControl(ImpersonationLevel=ImpersonationLevelOption.Identify)]检查您是否为远程服务器设置了最受限的必需级别。例如,如果服务器出于身份验证目的而需要识别您,但不需要模拟您,则使用上述识别级别。由于对可以在计算机之间传递的安全上下文的次数没有限制,所以请对Windows2000小心使用委派级别的模拟。WindowsServer2003引入了受限的委派。
注意在WindowsServer2003和Windows2000ServicePack4及更高版本中,不向所有用户授予模拟特权。
如果您的组件位于服务器应用程序中,则上述程序集级别属性在组件使用企业服务注册时控制组件的原始配置。
如果您的组件位于库应用程序中,则客户端进程确定模拟级别。如果客户端是ASP.NETWeb应用程序,请检查Machine.config文件中元素的comImpersonationLevel设置。
是否启用了基于角色的安全?检查是否启用了基于角色的安全。默认情况下,Windows2000中禁用它。检查您的代码是否包括以下属性:
[assembly:ApplicationAccessControl(true)]您是否使用了组件级别访问检查?如果在接口、组件或方法级别使用COM+角色(而不仅仅用于限制对应用程序的访问),则COM+角色是最有效的。检查您的代码是否包括以下属性:
[assembly:ApplicationAccessControl(AccessChecksLevel=AccessChecksLevelOption.ApplicationComponent)]另外,检查每个类是否使用ComponentAccessControl属性按如下进行了批注:
[ComponentAccessControl(true)]publicclassYourServicedComponent:ServicedComponent{}您是否在代码中执行了角色检查?如果方法代码调用ContextUtil.IsCallerInRole,请检查这些调用是否以对ContextUtil.IsSecurityEnabled的调用为开始。如果未启用安全,则IsCallerInRole始终返回“True”。检查您的代码是否在未启用安全时返回安全例外。
搜索您的代码来查找“ConstructionEnabled”,以便找到使用对象构建字符串的类。
[ConstructionEnabled(Default="")]publicclassYourServicedComponent:ServicedComponent,ISomeInterface如果使用对象构造函数字符串,请审查下列问题:
您是否在构造函数字符串中存储了敏感数据?如果存储了数据(例如连接字符串),请检查在COM+目录中存储之前是否对数据进行了加密。然后,您的代码应该在数据通过Construct方法传递到计算机时对数据进行加密。
您是否提供了默认构建字符串?如果数据在某中程度上是敏感的,请不要执行此操作。
您应该跨分布式应用程序的层进行审核。检查服务组件是否记录了操作和事务。可以通过SecurityCallContext对象获得原始调用方身份。只有当使用以下属性为进程级别和组件级别的检查配置应用程序的安全级别时,它才可用:
您是否将对象作为参数传递?
您是否使用了自定义身份验证和主要对象?
您如何配置代理凭据?
如果使用TcpChannel且您的组件API接受自定义对象参数,或者如果通过调用上下文传递自定义组件,则您的代码有两个安全漏洞。
如果作为参数传递的对象派生于System.MarshalByRefObject,则它通过引用传递。这种情况下,此对象需要URL,以支持回调到客户端。客户端URL可能受欺骗,会导致回调到备用计算机。
如果作为参数传递的对象支持序列化,则对象通过值传递。在此情况下,检查您的代码在服务器上取消序列化每个字段项时是否对每个字段项进行了验证,以避免恶意数据注入。
要防止自定义对象通过引用或通过值传递到远程组件,请将服务器方的格式化程序信道接收中的TypeFilterLevel属性设置为TypeFilterLevel.Low。
要找到在调用上下文中传递的对象,请搜索“ILogicalThreadAffinative”字符串。只有实现此接口的对象可以在调用上下文中传递。
如果使用了自定义身份验证,您是否依赖从客户端传递的主要对象?这存在潜在危险,因为恶意代码会创建包含扩展角色的主要对象以提升特权。如果使用此方法,请检查您是否只将它与带外机制(例如限制可以连接到您的组件的客户端计算机的IPSec策略)一同使用。
审查客户端代码如何配置远程代理中的凭据。如果使用了明确凭据,在何处维护这些凭据?它们应该加密,并存储在安全位置(例如受限的注册表项)。它们不应该以纯文本形式进行硬编码。理想情况下,您的客户端代码应该使用客户端进程令牌和使用默认凭据。
本部分列出您在审查数据访问代码时应该考虑的关键审查点。有关本部分中产生的问题的详细信息,请参阅模块14构建安全的数据访问。
您是否防止了SQL注入?
您是否使用了Windows身份验证?
您是否确保了数据库连接字符串的安全?
您如何确保数据库中敏感数据的安全?
您是否处理了ADO.NET例外?
您是否关闭了数据库连接?
通过使用Windows身份验证,您不用将凭据跨网络传递给数据库服务器,且连接字符串不用包含用户名和密码。Windows身份验证连接字符串或者使用Trusted_Connection='Yes',或者使用IntegratedSecurity='SSPI',如下列示例所示。
"server='YourServer';database='YourDatabase'Trusted_Connection='Yes'""server='YourServer';database='YourDatabase'IntegratedSecurity='SSPI'"您是否确保了数据库连接字符串的安全?审查您的代码是否正确和安全地使用了数据库连接字符串。这些字符串不应该进行硬编码或以纯文本存储在配置文件中,尤其是当连接字符串包括用户名和密码时。
搜索“Connection”,以找到ADO.NET连接对象的实例,并审查ConnectionString属性是如何设置的。
您是否对连接字符串进行了加密?检查代码是否检索了加密的连接字符串,以及然后是否对其进行了解密。代码应该将DPAPI用于加密,以避免关键管理问题。
您是否使用了空密码?请不要这样做。检查所有SQL帐户是否具有强密码。
您是否使用了sa帐户或其他高特权帐户?您是否使用了sa帐户或任何高特权帐户,例如sysadmin或db_owner角色的成员。这是一个常见错误。检查您是否将最低特权帐户与数据库中的受限权限一起使用。
您是否使用了PersistSecurityInfo?检查PersistSecurityInfo属性是否未设置为“True”或“Yes”,因为如果这样,则会导致在连接打开后可以从连接获取敏感信息(包括用户名和密码)。
如果在数据库中存储了敏感数据(例如信用卡号),如何确保数据的安全?您应该检查它是否使用强对称加密算法(例如3DES)进行了加密。
如果使用此方法,您如何确保3DES加密密钥的安全?您的代码应该使用DPAPI对3DES加密密钥进行加密,并在受限位置(例如注册表)中存储了加密密钥。
检查所有数据访问代码是否放置在try/catch块中,以及此代码是否根据您使用的ADO.NET数据提供程序处理SqlExceptions、OleDbExceptions或OdbcExceptions。
检查您的代码(例如发生异常时)没有因开放数据库连接而受到攻击。检查代码是否关闭了最后块中的连接,或者使用如下所示的语句在C#中构建了连接对象。这将自动确保它已关闭。
本模块已经讨论了如何审查托管代码中是否有主要的安全问题,包括XSS、SQL注入和缓冲区溢出。它还讨论了如何识别出可导致安全漏洞和成功攻击的其他更细微的缺陷。
安全代码审查不是万能药。但是,它们会非常有效,且应该作为开发生命周期中的常规里程碑。