查找源代码中的漏洞

迄今为止,我们介绍的攻击技巧全都需要与一个正在运行的应用程序进行交互;而且,从很大程度上讲,这些攻击主要由向应用程序提交专门设计的输入和监控其响应这两个步骤构成。本章将分析一种截然不同的漏洞查找方法:通过审查应用程序的源代码来查找漏洞。

在以下各种情况下,审查源代码有助于渗透测试员攻击目标Web应用程序。

一些应用程序为开源应用程序,或者使用开源组件,允许从相关资料库中下载它们的源代码,并从中寻找漏洞。

如果在提供咨询服务时执行渗透测试,应用程序所有者可能会允许查看应用程序的源代码,以提高审计的效率。

可能在应用程序中发现允许下载其源代码的文件泄露漏洞。

大多数应用程序使用某种客户端代码,如JavaScript;不需要任何权限即可访问它。

人们常常认为,如果想要进行代码审查,自身必须是一名经验丰富的程序员,并深入了解编写代码所使用的语言。然而,事实并非如此。一些编程经验有限的人也能够阅读并理解许多高级语言;同时,在Web应用程序常用的各种语言中,许多类型的漏洞的表现形式也基本相同。使用某种常规方法就可以完成绝大多数的代码审查,而且还可通过使用说明帮助理解所针对的语言与环境使用的相关语法及API。本章将介绍渗透测试员所需要的核心方法,并提供可能遇到的一些语言的使用说明。

代码审查方法

代码审查的方法很多,这些方法有助于提高在有限的时间内发现安全漏洞的效率。此外,还可以将代码审查与其他测试方法结合起来,充分利用每种方法的优势。

“黑盒”测试与“白盒”测试

前面各章描述的攻击方法常被称为黑盒(black-box)测试方法,因为它主要从外部攻击应用程序,并监控其输入与输出,而之前并不了解它的内部工作机制。相反,白盒(white-box)方法需要分析应用程序的内部运作,查阅所有设计文档、源代码与其他资料。

“白盒”代码审查可非常高效地发现应用程序中存在的漏洞。在审查源代码的过程中,我们常常可以迅速确定仅使用“黑盒”技巧很难或需要很长时间才能发现的漏洞。例如,通过阅读代码可迅速确定一个可访问任何用户账户的后门密码,但使用密码猜测攻击几乎不可能发现这个密码。

然而,代码审查并不能完全替代“黑盒”测试。当然,从某种程度上讲,应用程序中的全部漏洞都“存在于源代码中”;因此,理论上,通过代码审查可以确定所有这些漏洞。但是,使用“黑盒”方法可以更迅速、高效地发现许多漏洞。举例来说,使用第14章描述的自动化模糊测试技巧,每分钟可以向一个应用程序发送数百个测试字符串,它们将迅速分散到所有相关代码路径中,并立即返回响应。另外,通过向每个字段发送常见漏洞的触发器,常常可以在几分钟内确定大量通过代码审查需要数天才能发现的漏洞。而且,许多企业级应用程序的结构极其复杂,对用户提交的输入进行多层处理。同时,应用程序在每一个层面实施不同的控制与检查,一段源代码中的明显漏洞可能会被其他地方的代码完全消除。

大多数情况下,“黑盒”与“白盒”技巧可以相互补充,彼此强化。通常,通过代码审查初步查明一个漏洞后,再在一个正在运行的应用程序中对其进行测试,是确定该漏洞是否真实存在的最简单、最有效的方法。相反,在一个正在运行的应用程序中确定某种反常行为后,审查相关源代码往往是确定其根本原因的最佳途径。因此,如有可能,应适当结合使用“黑盒”与“白盒”的技巧,并根据实时测试过程中应用程序的反常行为、源代码的大小与复杂程度,调整在每种技巧上投入的时间与精力。

代码审查方法

任何功能比较强大的应用程序都可能包含成千上万行源代码,许多情况下审查代码时间有限,可能仅有几天时间。因此,有效代码审查的一个关键目标是,在有限的时间与精力条件下,确定尽可能多的安全漏洞。为了实现这个目标,我们有必要采用一种结构化的方法,使用各种技巧确保迅速确定源代码中存在的“明显漏洞”,为探查更微妙、更难发现的漏洞争取更多时间。

根据我们的经验,当审查Web应用程序源代码时,使用三重查找方法可迅速高效地确定其中存在的漏洞。这种方法由以下3个步骤组成。

(1)从进入点开始追踪用户向应用程序提交的数据,审查负责处理这些数据的代码。

(2)在代码中搜索表示存在常见漏洞的签名,并审查这些签名,确定某个漏洞是否确实存在。

(3)对内在危险的代码进行逐行审查,理解应用程序的逻辑,并确定其中存在的所有问题。需要进行仔细审查的功能组件包括:应用程序中的关键安全机制(验证、会话管理、访问控制与任何应用程序范围内的输入确认)、外部组件接口,以及任何使用本地代码(通常为C/C++)的情况。

首先分析各种常见的Web应用程序漏洞在源代码中的各种表现形式,以及当进行代码审查时如何以最简单的方式确定这些表现形式。这将有助于在代码中搜索漏洞签名[第(2)步]和仔细审查危险的代码[第(3)步]。

然后依次分析一些最流行的Web开发语言,确定应用程序如何获得用户提交的数据(通过请求参数、cookie等)、它如何与用户进行会话交互、每种语言中存在潜在危险的API以及每种语言的配置与环境对应用程序安全的影响。这将有助于我们从进入点开始追踪用户向应用程序提交的数据[第(1)步],并为其他步骤提供每种语言的参考。最后介绍一些进行代码审查的有用工具。

 注解 当进行代码审查时,应该始终记住,应用程序可能扩展了类库与接口,对标准API调用执行了包装器,并对安全性至关重要的任务(如保存关于每个会话的信息)采用了定制机制。在进行仔细的代码审查之前,必须了解这些定制的范围,并相应调整审查方法。

常见漏洞签名

许多类型的Web应用程序漏洞在代码中都有相对一致的签名;通常,这表示通过迅速扫描和搜索代码就可以确定一个应用程序中存在的大部分漏洞。下面列举的示例出现在各种语言中,但在大多数情况下,签名是不区分语言的。最重要的是程序员采用的编程技巧,而不是实际使用的API和语法。

跨站点脚本

在非常明显的XSS漏洞中,用户收到的HTML代码的一部分明显是由用户可控制的数据构成的。在下面的代码中,HREF链接的目标即由从请求查询字符串中直接提取的字符串构成:

这时,对潜在恶意的内容进行HTML编码这种阻止跨站点脚本的常用方法,并不适用于生成的串联字符串,因为它已经包含有效的HTML标记;任何净化数据的尝试都会按应用程序指定的方式对HTML编码,从而中断应用程序。因此,这个示例肯定易于受到攻击,除非在其他地方实施了过滤,阻止了在查询字符串中包含XSS的请求。这种使用过滤来阻止XSS攻击的方法往往存在缺陷,如果采用,应对它进行仔细审查,以确定解决办法(请参阅第12章了解相关内容)。

在更加微妙的情况下,用户可控制的数据用来指定随后用于创建发送给用户响应的一个变量值。在下面的示例中,类成员变量m_pageTitle被设定为从请求查询字符串中提取的一个值,随后将用于创建被返回的HTML页面的<title>元素:

当遇到这种代码时,渗透测试员应该仔细审查应用程序对m_pageTitle变量的处理过程,以及它是如何被合并到被返回的页面中的,以确定数据是否为防止XSS攻击而进行了适当的编码。

前面的示例明确证明代码审查在查找一些漏洞时的重要作用。XSS漏洞只有在一个不同的参数(type)指定一个特殊的值(3)时才会触发。标准的模糊测试与对相关请求进行漏洞扫描都无法发现这种漏洞。

SQL注入

如果各种硬编码的字符串与用户可控制的数据串联成一个SQL查询,然后在数据库内执行这个查询,那么最可能出现SQL注入漏洞。在下面的代码中,查询由直接从请求查询字符串中提取的数据构成:

在源代码中搜索常用于从用户提交的输入构建查询的硬编码子字符串,是在代码中迅速确定这种明显漏洞的简单方法。这些子字符串通常由SQL代码片断组成,并被源代码引用;因此,寻找由引号、SQL关键字和空格组成的适当模式可能会有用。例如:

在每一种情况中,应该核实将这些字符串与用户可控制的数据串联是否会引入SQL注入漏洞。因为SQL关键字区分大小写,所以在代码中搜索这些关键字时也应区分大小写。请注意,为减少错误警报,这些搜索项后可能附加了空格。

路径遍历

用户可控制的输入未经任何输入确认,或者核实已经选择一个适当的文件,就被传送给一个文件系统API,这是路径遍历漏洞的常见签名。在最常见的情况下,用户数据附加在一个硬编码或系统指定的目录路径之后,让攻击者能够使用点-点-斜线建立目录树,访问其他目录中的文件。例如:

应对任何允许用户上传或下载文件的应用程序功能进行仔细审查,了解它是如何根据用户提交的数据调用文件系统API的,并确定是否可以使用专门设计的输入访问其他位置的文件。通常,通过在代码中搜索任何与文件名有关的查询字符串参数(在本例中为AttachName),以及在相关语言中搜寻所有文件API并检查提交它们的参数,就可以迅速确定相关的功能。(常用语言中的相关API列表见后文。)

任意重定向

通过源代码中的签名常可轻易确定各种钓鱼攻击向量,如任意重定向。在下面这个示例中,查询字符串中用户提交的数据被用于构建一个URL,用户即被重定向到这个URL:

通常,检查客户端代码即可发现任意重定向漏洞;当然,这并不需要了解应用程序的内部机制。在下面的示例中,使用JavaScript从URL查询字符串中提取一个参数,并最终重定向到这个URL:

可见,这段脚本的作者已意识到,该脚本可能会成为指向外部域的一个绝对URL的重定向攻击的目标。该脚本检查重定向URL是否包含一个双斜线(像http://中一样),如果包含,就省略第一个单斜线,将它转换成一个相对URL。但是,当它最后一次调用unescape()函数时,该函数将对任何URL编码的字符进行解码。确认后再执行规范化常常会导致漏洞产生(请参阅第2章了解相关内容)。在这个示例中,攻击者可以使用以下查询字符串造成一个指向任意绝对URL的重定向:

OS命令注入

连接到外部系统的代码中常常包含指示代码注入漏洞的签名。在下面的示例中,message与address参数从用户可控制的表单数据中提取出来,然后直接传送给UNIX system API:

后门密码

除非被恶意程序员有意隐藏,否则,当审查证书确认逻辑时,用于测试或管理目的的后门密码非常容易确定。例如:

同样,使用这种方法还可以轻易确定未引用的函数与隐藏调试参数。

本地代码漏洞

应对应用程序使用的任何本地代码进行仔细审查,确定可被攻击者用于执行任意代码的常见漏洞。

缓冲区溢出漏洞

这些漏洞常常使用一个未经检查的API实现对缓冲区的操控。这些API数量众多,包括strcpy、strcat、memcpy与sprintf以及它们的wide-char和其他变体。确定代码中明显的缓冲区溢出漏洞的一种简单方法是,搜索所有这些API的用法,并检实来源缓冲区是否可由用户控制,以及代码是否已经明确确定目标缓冲区足够大,能够容纳被复制到它里面的数据(因为API不会这样做)。

我们可以轻易确定以易受攻击的方式调用危险API的做法。在下面的示例中,用户可控制的字符串pszName被复制到一个固定大小的栈缓冲区中,但之前并没有检查该缓冲区是否足以容纳这些字符串:

请注意,以一个安全的API替代未经检查的API,这种方法并不能保证缓冲区溢出不会发生。有时,由于错误或误解,一个经过检查的API以危险的方式被使用,以前面漏洞的“修复代码”为例:

因此,要在代码中彻底搜索缓冲区溢出漏洞,必须对整个代码进行仔细地逐行审查,追踪对用户可控制的数据执行的每一项操作。

整数漏洞

这些漏洞的表现形式各异,而且非常难以检测;但有时通过源代码中的签名却可立即确定这类漏洞。

比较有符号与无符号的整数时经常会出现问题。以下代码中,上一个漏洞的“修复代码”对有符号的整数(len)与无符号的整数(sizeof(strFileName))进行比较。如果用户能够使len为负值,这个比较就会成功进行,而且未经检查的strcpy仍会运行:

格式化字符串漏洞

通常,通过检查printf与FormatMessage系列函数的用法,如果发现格式化字符串参数并未硬编码,而是由用户控制,就可以立即确定这类漏洞。以下面这段调用fprintf函数的代码为例:

源代码注释

许多软件漏洞可以从源代码注释中发现。如果开发者意识到某项操作存在危险,并在代码中标注提示,准备以后修复这个问题,但却从未着手修复,这时就会出现以上情况。另外,如果测试时确定应用程序存在某种反常行为,并将其记录在注释中,但同样从未对这种行为进行全面调查,这时也会出现上面的情况。例如,我们曾在一个应用程序的生产代码中遇到以下代码:

在代码中搜索说明常见问题的注释,往往可以迅速发现许多明显的漏洞。下面是一些已证明有用的搜索项:

bug

problem

bad

hope

todo

fix

overflow

crash

inject

xss

trust

Java 平台

本节主要介绍在Java平台上获取用户提交的输入的方法、与用户会话交互的方式、存在的潜在危险的API以及与安全相关的配置选项。

确定用户提交的数据

Java应用程序通过javax.servlet.http.HttpServletRequest接口获取用户提交的输入,该接口对javax.servlet.ServletRequest接口进行了扩展。这两个接口中包含了大量Web应用程序用于访问用户提交的数据的API。表19-1列出的API可用于获取用户请求中的数据。

表19-1 Java平台中用于获取用户提交的数据的API

API 描 述

getParameter

getParameterNames

getParameterValues

getParameterMap 以String名称与String值之间映射的形式保存URL查询字符串与

POST请求主体中的参数,使用这些API可以访问该映射

getQueryString 返回请求中的整个查询字符串,可以它代替getParameter API

getHeader

getHeaders

getHeaderNames 以String名称与String值之间映射的形式保存请求中的HTTP消息头,使用这些API可以访问该映射

getRequestURI

getRequestURL 这些API返回请求中的URL,包括查询字符串

getCookies 返回Cookie对象的一个数组,其中包含请求所收到的cookie信息,包括

它们的名称与值

getRequestedSessionId 在某些情况下用来替代getCookies,返回在请求中提交的会话ID值

getInputStream

getReader 这些API返回客户端送出的原始请求的不同表示形式,因此可用于访问其

他所有API获得的任何信息

getMethod 返回HTTP请求所使用的方法

getProtocol 返回HTTP请求所使用的协议

getServerName 返回HTTP Host消息头的值

getRemoteUser

getUserPrincipal 如果当前用户通过验证,这些API返回用户的信息,包括登录名。如果用

户可以在自我注册过程中选择自己的用户名,这种做法可能会在应用程

序的处理过程中引入恶意输入

会话交互

Java平台应用程序使用javax.servlet.http.HttpSession接口保存和检索当前会话中的信息。每会话存储(per-session storage)是字符串名称与对象值之间的一个映射。表19-2列出的API用于保存和检索会话中的数据。

表19-2 Java平台中用于与用户会话交互的API

API 描 述

setAttribute

putValue 用于保存当前会话中的数据

getAttribute

getValue

getAttributeNames

getValueNames 用于查询保存在当前会话中的数据

潜在危险的API

这一节介绍一些常见的Java API。以危险的方式使用这些API可能会造成安全漏洞。

文件访问

在Java中,用于访问文件与目录的主要的类为java.io.File。从安全的角度看,这个类的最重要的用法是调用它的构造函数,该构造函数接受一个父目录和文件名,或者仅为一个路径名。

无论以哪种方式使用构造函数,如果未检查其中是否包含点-点-斜线序列就将用户可控制的数据作为文件名参数提交,那么可能会造成路径遍历漏洞。例如,下面的代码将打开Windows C:\驱动器根目录下的一个文件:

在Java中,常用于读取与写入文件内容的类包括:

java.io.FileInputStream

java.io.FileOutputStream

java.io.FileReader

java.io.FileWriter

这些类从它们的构造函数中提取File对象,或者通过文件名字符串打开文件。如果用户可控制的数据作为这个参数提交,同样可能会引入路径遍历漏洞。例如:

数据库访问

下面这些是常用于以SQL查询执行任何一个字符串的API:

java.sql.Connection.createStatement

java.sql.Statement.execute

java.sql.Statement.executeQuery

如果用户提交的数据属于以查询执行的字符串的一部分,那么它可能易于受到SQL注入攻击。例如:

它执行不良查询:

下面的API更加稳定可靠,能够替代前面描述的API,允许应用程序创建一个预先编译的SQL语句,并以可靠且类型安全的方式指定它的参数占位符的值:

java.sql.Connection.prepareStatement

java.sql.PreparedStatement.setString

java.sql.PreparedStatement.setInt

java.sql.PreparedStatement.setBoolean

java.sql.PreparedStatement.setObject

java.sql.PreparedStatement.execute

java.sql.PreparedStatement.executeQuery

当然还有许多,此处不一一列出。

如果按正常的方式使用,这些API就不易受到SQL注入攻击。例如:

它生成的查询等同于:

动态代码执行

Java 语言本身并不包含任何动态评估Java源代码的机制,尽管一些应用(主要在数据库产品中)提供了评估方法。如果所审查的应用程序动态构建任何Java代码,就应该了解应用程序如何构建这些代码,并决定用户可控制的数据是否以危险的方式使用。

OS命令执行

下面的API用于在Java应用程序中执行外部操作系统命令:

java.lang.runtime.Runtime.getRuntime

java.lang.runtime.Runtime.exec

如果提交给exec的字符串参数完全可由用户控制,那么几乎可以肯定应用程序易于受到任何命令执行攻击。例如,下面的代码将运行Windows calc程序:

然而,如果用户仅能够控制提交给exec的部分字符串,那么应用程序可能不易于受到攻击。在下面的示例中,用户可控制的数据以命令行参数的形式提交给记事本进程,引起它尝试加载| calc文档:

exec API本身并不解释&与|等shell元字符,因此这个攻击失败。

有时,仅控制部分字符串提交给exec仍然足以执行任意命令;例如下面这个稍微不同的示例(注意notepad后面缺少一个空格):

通常,在这种情况下,应用程序将易于受到除代码执行以外的攻击。例如,如果应用程序以用户可控制的参数作为目标URL执行wget程序,那么攻击者就可以向wget进程传递危险的命令行参数,例如,致使它下载一个文档,并将该文档保存在文件系统中的任何位置。

URL重定向

下面的API用于在Java中发布HTTP重定向:

javax.servlet.http.HttpServletResponse.sendRedirect

javax.servlet.http.HttpServletResponse.setStatus

javax.servlet.http.HttpServletResponse.addHeader

通常,使用sendRedirect方法可以引起一个重定向响应,该方法接受一个包含相对或绝对URL的字符串。如果这个字符串的值由用户控制,那么应用程序可能易于受到钓鱼攻击。

还应该审查setStatus与addHeader API的所有用法。如果某个重定向包含一个含有HTTP Location消息头的3xx响应,应用程序就可能使用这些API执行重定向。

套接字

java.net.Socket类从它的构造函数中提取与目标主机和端口有关的各种信息,如果用户能够以某种方式控制这些信息,攻击者就可以利用应用程序与任意主机建立网络连接,无论这些主机位于因特网上、私有DMZ中还是在应用程序上运行的内部网络内。

配置Java环境

web.xml文件包含Java平台环境的配置设置,同时它还控制着应用程序的行为。如果应用程序使用容器安全管理,那么验证与授权将在web.xml文件中,根据被保护的每一个资源或资源集,于应用程序代码以外声明。可在web.xml文件中设置的配置选项如表19-3所示。

表19-3 Java环境中与安全有关的配置设置

设 置 描 述

login-config login-config 元素配置验证细节

两类验证分别为forms-based (页面由form-login-page 元素指定)与在

auth-method元素中指定的Basic Auth或Client-Cert

如果使用基于表单的验证,指定的表单必须将操作定义为j_security_check,并必

须提交j_username与j_password参数。Java应用程序将把它当做一个登录请求处理

security-constraint 如果定义了login-config元素,就可以使用security-constraint元素限定资

源。这个元素可用于定义受保护的资源

在security-constraint元素中,可以使用url-pattern元素定义资源集。例如:

<url-pattern>/admin/*</url-pattern>

分别在role-name与principal-name元素中定义的角色与主要用户可以访问这些

资源

session-config session-timeout元素配置会话超时(单位:分钟)

error-page error-page元素定义应用程序如何处理错误。通过error-code与exceptiontype

元素可单独处理HTTP错误代码与Java异常

init-param init-param元素配置各种初始化参数。其中包括与安全有关的设置:

listings应设置为false

debug应设置为0

Servlet可以使用HttpServletRequest.isUserInRole访问Servlet代码中的相同角色信息,实施编程检查。映射项security-role-ref将内置的角色检查与对应的容器角色连接起来。

除web.xml文件外,不同应用程序服务器还可能使用包含其他安全相关设置的次要部署文件(如weblogic.xml文件),当分析环境配置时,应检查这些设置。

ASP.NET

本节主要介绍在ASP.NET平台上获取用户提交的输入的方法、与用户会话交互的方式、其中存在的潜在危险的API以及与平台安全相关的配置选项。

确定用户提交的数据

ASP.NET 应用程序通过System.Web.HttpRequest类获取用户提交的输入。这个类中包含大量Web应用程序用于访问用户提交的数据的属性和方法。表19-4列出的API可用于获取用户请求中的数据。

表19-4 ASP.NET平台中用于获取用户提交的数据的API

API 描 述

Params 以String名称与String值的形式保存URL查询字符串、POST请求主体、HTTP

cookie以及其他服务器变量中的参数。这个属性返回这些参数类型的集合

Item 返回Param集合中的命名项

Form 返回用户提交的表单变量名称与变量值集合

QueryString 返回请求查询字符串中变量名称与变量值的集合

ServerVariables 返回大量ASP服务器变量(类似于CGI变量)名称与变量值的集合,包括请求原始

数据、查询字符串、请求方法、HTTP Host消息头等

Headers 以String名称与String值之间的映射的形式保存请求中的HTTP消息头,使用这

个属性可以访问该映射

Url

RawUrl 这些属性返回请求中的URL信息,包括查询字符串

UrlReferer 返回与在请求的HTTP Referer消息头中指定的URL有关的信息

Cookies 返回Cookie对象的集合,其中包含请求所收到的cookie的信息,包括它们的名称

与值

Files 返回用户上传的文件集合

InputStream

BinaryRead 这些API返回客户端送出的原始请求的不同表示形式,因此可用于访问其他所有API

获得的任何信息

HttpMethod 返回HTTP请求所使用的方法

Browser

UserAgent 返回用户浏览器的信息,与在HTTP User-Agent消息头中提交的信息类似

AcceptTypes 返回客户端支持的MIME类型的字符串数组,与在HTTP Accept消息头中提交的信

息类似

UserLanguages 返回包含客户端所接受的语言的字符串数组,与在HTTP Accept-Language消息

头中提交的信息类似

会话交互

ASP.NET应用程序以各种方式与用户会话进行交互,以保存和检索信息。

使用Session属性可轻松保存和检索当前会话中的信息。这个属性的访问方式与任何其他索引集合类似:

ASP.NET个性化配置与Session属性的用法非常相似,其唯一不同之处在于,前者相对于一个特定的用户,因此在相同用户的不同会话中持续保存不变。在不同的会话中,用户的身份通过验证机制或一个特殊的持久性cookie得以重新确认。在用户个性化配置中,数据以下列方式保存和检索:

另外,System.Web.SessionState.HttpSessionState类也可用于保存和检索会话中的信息。它以字符串名称与对象值之间映射的方式保存信息,使用表19-5中列出的API可以访问这个映射。

表19-5 ASP.NET平台中用于与用户会话交互的API

API 描 述

Add 在会话集合中增加一个数据项

Item 获取或设定集合中命名数据项的值

Keys

GetEnumerator 返回集合中所有数据项的名称

CopyTo 将值组成的集合复制到数组中

潜在危险的API

这一节介绍一些常见的ASP.NET API。以危险的方式使用这些API可能会造成安全漏洞。

文件访问

System.IO.File是用于访问ASP.NET文件最主要的类。它的所有方法都是静态的,并且没有公共构造函数。

这个类的37个方法全都接受一个文件名作为参数。如果未检查其中是否包含点-点-斜线序列,就提交用户可控制的数据,就会造成路径遍历漏洞。例如,下面的代码将打开Windows C:\ 驱动器根目录下的一个文件:

下面的类常用于读取与写入文件内容:

System.IO.FileStream

System.IO.StreamReader

System.IO.StreamWriter

它们的各种构造函数接受一个文件路径作为参数。如果提交用户可控制的数据,这些构造函数可能引入路径遍历漏洞。例如:

数据库访问

ASP.NET有许多用于访问数据库的API,下面的类主要用于建立并执行SQL语句:

System.Data.SqlClient.SqlCommand

System.Data.SqlClient.SqlDataAdapter

System.Data.Oledb.OleDbCommand

System.Data.Odbc.OdbcCommand

System.Data.SqlServerCe.SqlCeCommand

其中每个类都有一个构造函数,它接受一个包含SQL语句的字符串;而且每个类都有一个CommandText属性,可用于获取并设定SQL语句的当前值。如果适当地配置一个命令对象,通过调用Execute方法即可执行SQL语句。

如果用户提交的数据属于以查询执行的字符串的一部分,那么应用程序可能易于受到SQL注入攻击。例如:

它会执行不良查询:

上面列出的每一个类通过它们的Parameters属性支持预处理语句,允许应用程序创建一个包含参数占位符的SQL语句,并以可靠且类型安全的方式设定这些占位符的值。如果按正常的方式使用,这种机制就不易受到SQL注入攻击。例如:

它生成的查询等同于:

动态代码执行

VBScript函数Eval接受一个包含VBScript表达式的字符串自变量。该函数求出这个表达式的值,并返回结果。如果用户可控制的数据被合并到要计算值的表达式中,那么用户就可以执行任意命令或修改应用程序的逻辑。

函数Execute和ExecuteGlobal接受一个包含ASP代码的字符串,这个ASP代码与直接出现在脚本中的代码的执行方式完全相同。冒号分隔符用于将几个语句连接在一起。如果向Execute函数提交用户可控制的数据,那么攻击者就可以在应用程序中执行任意命令。

OS命令执行

下面的API可以各种方式在ASP.NET应用程序中运行外部进程:

System.Diagnostics.Start.Process

System.Diagnostics.Start.ProcessStartInfo

在对对象调用Start之前,可以向静态Process.Start方法提交一个文件名字符串,或者用一个文件名配置Process对象的StartInfo属性。如果文件名字符串可完全由用户控制,那么应用程序几乎可以肯定易于受到任意命令执行攻击。例如,下面的代码将运行Windows calc程序:

然而,如果用户仅能够控制提交给Start的部分字符串,那么应用程序仍然可能易于受到攻击。例如:

API并不解释& 与 | 等 shell元字符,也不接受文件名参数中的命令行参数,因此,如果用户仅控制文件名参数的一部分,这种攻击是唯一能够成功的攻击。

已被启动的进程的命令行参数可以使用ProcessStartInfo类的Arguments属性设定。如果只有 Arguments参数可由用户控制,应用程序仍然易于受到除代码执行以外的其他攻击。例如,如果应用程序以用户可控制的参数作为目标URL执行wget程序,那么攻击者就可以向wget进程提交危险的命令行参数,例如,致使它下载一个文档,并将该文档保存在文件系统中的任何位置。

URL重定向

下面的API用于在ASP.NET中发布一个HTTP重定向:

System.Web.HttpResponse.Redirect

System.Web.HttpResponse.Status

System.Web.HttpResponse.StatusCode

System.Web.HttpResponse.AddHeader

System.Web.HttpResponse.AppendHeader

Server.Transfer

通常,使用HttpResponse.Redirect方法可以引起一个重定向响应,该方法接受一个包含相对或绝对URL的字符串。如果这个字符串的值由用户控制,那么应用程序可能易于受到钓鱼攻击。

还必须确保检查Status/StatusCode属性与AddHeader/AppendHeader方法的用法。如果某个重定向包含一个含有HTTP Location消息头的3xx响应,应用程序就可能使用这些API执行重定向。

Server.Transfer方法有时也可用于实现重定向。实际上,这个方法并不能实现HTTP重定向,而是应根据当前请求修改被服务器处理的页面。因此,不能通过破坏它重定向到一个站外URL;这个方法对攻击者而言并没有多大用处。

套接字

System.Net.Sockets.Socket类用于创建网络套接字。创建一个Socket对象后,再通过调用Connect方法连接这个对象;该方法接受目标主机的IP与端口信息为参数。如果用户能够以某种方式控制这些主机信息,攻击者就可以利用应用程序与任意主机建立网络连接,无论这些主机位于因特网上、私有DMZ中还是在应用程序上运行的内部网络内。

配置ASP.NET环境

Web根目录下的web.config XML文件包含ASP.NET环境的配置设置,它还控制着应用程序的行为,如表19-6所示。

表19-6 ASP.NET环境中与安全有关的配置设置

设 置 描 述

httpCookies 这个元素决定与cookie有关的安全设置。如果httpOnlyCookies属性为真,那么cookie将被

标记为HttpOnly,因而不能被客户端脚本直接访问。如果requireSSL属性为真,cookie

将被标记为secure,因此只能由浏览器通过HTTPS请求传送

sessionState 这个元素决定会话的行为。timeout属性的值决定一个空闲会话的到期时间(单位:分钟)。

如果regenerateExpiredSessionId元素设为true(默认情况),那么在收到一个到期

会话ID后,将发布一个新的会话ID

compilation 这个元素决定是否将调试符号编译到页面中,生成更详细的调试错误信息。如果debug属性

为true,调试符号将包含在页面中

customErrors 这个元素决定,如果发生无法处理的错误,应用程序是否返回详细的错误消息。如果mode属

性为On或RemoteOnly,那么应用程序用户将收到被属性defaultRedirect确认的页面,

而不是系统生成的详细消息

httpRuntime 这个元素决定各种运行时设置。如果enableHeaderChecking属性为true(默认情况),

ASP.NET将在请求消息头中检查潜在的注入攻击,包括跨站点脚本。如果enable-VersionHeader

属性为true(默认情况),ASP.NET将输出一个详细的版本字符串,攻击

者可利用它在特定版本的平台中搜索漏洞

如果数据库连接字符串之类的敏感数据保存在配置文件中,应使用ASP.NET“受保护配置”特性加密这些数据。

PHP

本节主要介绍在PHP平台上获取用户提交的输入的方法、与用户会话交互的方式、其中存在潜在危险的API以及与平台安全相关的配置选项。

确定用户提交的数据

PHP使用一系列数组变量保存用户提交的数据,如表19-7所示。

表19-7 PHP平台中用于获取用户提交的数据的变量

变 量 描 述

$_GET

$HTTP_GET_VARS 这个数组包含在查询字符串中提交的参数。这些参数根据其名称访

问。例如,在下面的URL中:

https://wahh-app.com/search.php?query=foo

查询参数的值使用以下代码访问:

$_GET[‘query’]

$_POST $HTTP_POST_VARS 这个数组包含在请求主体中提交的参数

$_COOKIE $HTTP_COOKIE_VARS 这个数组包含在请求主体中提交的cookie

$_REQUEST 这个数组包含$_GET、$_POST与$_COOKIE数组中的所有数据

$_FILES $HTTP_POST_FILES 这个数组包含在请求中上传的文件

$_SERVER[‘REQUEST_METHOD’] 包含在HTTP请求中使用的方法

$_SERVER[‘QUERY_STRING’] 包含在请求中提交的完整查询字符串

$_SERVER[‘REQUEST_URI’] 包含在请求中提交的完整URL

$_SERVER[‘HTTP_ACCEPT’] 包含HTTP Accept消息头的内容

$_SERVER[‘HTTP_ACCEPT_CHARSET’] 包含HTTP Accept-charset消息头的内容

$_SERVER[‘HTTP_ACCEPT_ENCODING’] 包含HTTP Accept-encoding消息头的内容

$_SERVER[‘HTTP_ACCEPT_LANGUAGE’] 包含HTTP Accept-language消息头的内容

$_SERVER[‘HTTP_CONNECTION’] 包含HTTP Connection消息头的内容

$_SERVER[‘HTTP_HOST’] 包含HTTP Host消息头的内容

$_SERVER[‘HTTP_REFERER’] 包含HTTP Referer消息头的内容

$_SERVER[‘HTTP_USER_AGENT’] 包含HTTP User-agent消息头的内容

$_SERVER[‘PHP_SELF’] 包含当前运行脚本的名称。虽然攻击者无法控制脚本名称,但可以

在这个名称后附加路径信息。例如,如果一个脚本包含以下代码:

<form action=“<?= $_SERVER[‘PHP_SELF’] ?>”>

那么攻击者就可以设计诸如下面的跨站点脚本攻击:

/search.php/"><script>

当尝试确定PHP应用程序如何访问用户提交的输入时,应该记住以下反常情况。

$GLOBALS是一个包含在脚本全局范围内定义的所有变量的引用的数组。使用它可以根据名称访问其他变量。

如果配置指令register_globals被激活,PHP会为所有请求参数(即$_REQUEST数组中的全部数据)建立全局变量。这表示,应用程序可通过与相关参数相同的名称引用一个变量,从而访问用户输入。如果应用程序使用这种方法访问用户提交的数据,那么只有仔细地逐行审查代码,才能确定以这种方式使用的变量。

除前面提到的标准HTTP消息头外,PHP还在$_SERVER数组中增加了一个数据,用于处理在请求中收到的任何定制HTTP消息头。例如,提交消息头:

生成:

名称包含下标(方括号内)的输入参数被自动转换为数组。例如,请求下面的URL:

将使$_GET[‘query’]变量的值转换成一个包含两个成员的数组。如果一个数组被提交给一个希望收到标量值的函数,可能会在应用程序中出现无法预料的行为。

会话交互

PHP使用$_SESSION数组保存和检索用户会话中的信息。例如:

$HTTP_SESSION_VARS数组的用法与上面的数组相同。

如果register_globals被激活(见19.5.4节),那么全局变量将通过以下方式保存在当前会话中:

潜在危险的API

这一节介绍一些常见的PHP API。以危险的方式使用这些API可能会造成安全漏洞。

文件访问

PHP中包含大量用于访问文件的函数,其中许多接受可用于访问远程文件的URL和其他结构。

下面的函数用于读取或写入一个指定文件的内容。如果向这些API提交用户可控制的数据,攻击者就可以利用这些API访问服务器文件系统上的任意文件。

fopen

readfile

file

fpassthru

gzopen

gzfile

gzpassthru

readgzfile

copy

rename

rmdir

mkdir

unlink

file_get_contents

file_put_contents

parse_ini_file

下面的函数用于包含并执行一个指定的PHP脚本。如果攻击者能够使应用程序执行受控的文件,他就可以在服务器上执行任意命令。

include

include_once

require

require_once

virtual

请注意,即使无法包含远程文件,但如果攻击者可向服务器上传任意文件,他仍然能够执行任意命令。

PHP配置选项allow_url_fopen可用于防止一些文件函数访问远程文件。但是,在默认情况下,这个选项设为1(表示允许远程文件);因此,表19-8中列出的协议可用于检索远程文件。

表19-8 可用于检索远程文件的网络协议

协 议 示 例

HTTP,HTTPS http://wahh-attacker.com/bad.php

FTP ftp://user:password@wahh-attacker.com/bad.php

SSH ssh2.shell://user:pass@wahh-attacker.com:22/xterm

ssh2.exec://user:pass@wahh-attacker.com:22/cmd

即使allow_url_fopen设为0,攻击者仍然可以使用表19-9列出的方法访问远程文件(取决于所安装的扩展)。

表19-9 allow_url_fopen设为0时仍然可用于访问远程文件的方法

方 法 示 例

SMB \\wahh-attacker.com\bad.php

PHP输入/输出流 php://filter/resource=http://wahh-attacker.com/bad.php

压缩流 compress.zlib://http://wahh-attacker.com/bad.php

音频流 ogg://http://wahh-attacker.com/bad.php

 注解 PHP5.2以后的版本引入了一个新的选项allow_url_include,默认情况下,该选项被禁用。这个默认的配置防止前面提到的方法在调用文件包含函数时用于指定一个远程文件。

数据库访问

下面的函数用于向数据库发送一个查询并检查查询结果:

mysql_query

mssql_query

pg_query

SQL语句以一个简单的字符串提交。如果用户可控制的数据属于字符串参数的一部分,那么应用程序就可能容易受到SQL注入攻击。例如:

它会执行不良查询:

下面的函数可用于创建预处理语句,允许应用程序建立一个包含参数占位符的SQL查询,并以可靠而且类型安全的方式设定这些占位符的值:

mysqli->prepare

stmt->prepare

stmt->bind_param

stmt->execute

odbc_prepare

如果按照正常的方式使用,这种机制就不易受到SQL注入攻击。例如:

它生成的查询等同于:

动态代码执行

下面的函数可用于动态执行PHP代码:

eval

call_user_func

call_user_func_array

call_user_method

call_user_method_array

create_function

分号分隔符用于将几个语句连接在一起。如果向这些函数提交用户可控制的数据,那么应用程序可能易于受到脚本注入攻击。

搜索与替代正则表达式的preg_replace函数,如果以/e选项调用,可用于运行一段特殊的PHP代码。如果用户可控制的数据出现在动态执行的PHP代码中,应用程序可能易于受到攻击。

PHP的另一个有趣的特点在于,它可以通过一个包含函数名称的变量动态调用该函数。例如,下面的代码将调用在查询字符串func参数中指定的函数:

这时,用户可以通过修改func参数的值,使应用程序调用任意一个函数(没有参数)。例如,调用phpinfo函数将使应用程序输出大量与PHP环境有关的信息,包括配置选项、操作系统信息与扩展。

OS命令执行

下面这些函数可用于执行操作系统命令:

exec

passthru

popen

proc_open

shell_exec

system

反单引号(`)

所有这些命令都可以使用 | 字符链接在一起。如果未经过滤就向这些函数提交用户可控制的数据,那么攻击者就可以在应用程序中执行任意命令。

URL重定向

下面的API用于在PHP中发布一个HTTP重定向:

http_redirect

header

HttpMessage::setResponseCode

HttpMessage::setHeaders

通常,使用http_redirect函数可以实现一个重定向,该函数接受一个包含相对或绝对URL的字符串。如果这个字符串的值由用户控制,那么应用程序可能易于受到钓鱼攻击。

通过调用包含适当Location消息头的header函数也可以实现重定向,它让PHP得出结论,认为需要一个HTTP重定向。例如:

还应仔细审查setResponseCode与setHeaders API的用法。如果某个重定向包含一个含有HTTP Location 消息头的3xx响应,应用程序就可能使用这些API执行重定向。

套接字

下面的API用于在PHP中建立和使用网络套接字:

socket_create

socket_connect

socket_write

socket_send

socket_recv

fsockopen

pfsockopen

使用socket_create创建一个套接字后,再通过调用socket_connect与远程主机建立连接;这个API接受目标主机的IP与端口信息为参数。如果用户能够以某种方式控制这些主机信息,攻击者就可以利用应用程序与任意主机建立网络连接,无论这些主机位于公共因特网上、私有DMZ中还是应用程序运行的内部网络。

fsockopen与pfsockopen函数可用于打开连接指定主机与端口的套接字,并返回一个可用在fwrite和fgets等标准文件函数中的文件指针。如果向这些函数提交用户数据,应用程序就可能易于受到攻击,如前文所述。

配置PHP环境

PHP配置选项在php.ini文件中指定,该文件使用与Windows INI文件相同的结构。有各种选项都会影响一个应用程序的安全。最新版的PHP删除了许多以前引起问题的选项。

使用全局变量注册

如果register_globals指令被激活,PHP会为所有请求参数建立全局变量。如果PHP不要求变量在使用前被初始化,这个选项就会导致安全漏洞,使攻击者能够将一个变量初始化为任意一个值。

例如,下面的代码检查一名用户的证书,如果证书有效,就将$authenticated变量值设为1:

因为最初PHP没有将$authenticated变量明确地初始化为0,攻击者就可以通过提交请求参数authenticated=1避开登录。这使PHP在进行证书检查之前就将全局变量$authenticated设为1。

 注解 从PHP 4.2.0开始,register_globals指令默认被禁用。然而,由于许多老式应用程序依赖于register_globals执行的正常操作,因此,通常php.ini会明确激活该指令。PHP 6完全删除了register_globals指令。

安全模式

如果safe_mode指令被激活,那么PHP会对使用某些危险的函数施加限制。一些函数被完全禁用,其他一些函数的使用也受到限制,如下所示。

shell_exec函数被禁用,因为这个函数可用于执行操作系统命令。

mail函数的additional_parameters参数被禁用,因此,如果以不安全的方式使用这个参数,可能导致SMTP注入漏洞(请参阅第10章了解相关内容)。

exec函数仅能够执行safe_mode_exec_dir指定目标下的可执行程序,命令字符串中的元字符被自动转义。

 注解 并非所有的危险函数都受到安全模式的限制,一些限制受到其他配置选项的影响。而且,有各种方法可以避开一些安全模式限制。安全模式并不能完全解决PHP应用程序中的安全问题。PHP 6已删除安全模式。

magic quotes

如果激活magic_quotes_gpc指令,那么请求参数中包含的任何单引号、双引号、反斜线和空字符都会用一个反斜线自动转义。如果magic_quotes_sybase指令被禁用,那么PHP就会用一个单引号转义所有单引号。这个选项旨在保护包含不安全的数据库调用的危险代码,以防它被恶意的用户输入利用。在应用程序的代码中查找SQL注入漏洞时,应该检查magic quotes是否被激活,因为它会影响应用程序处理输入的方式。

使用magic quotes并不能防止所有SQL注入攻击。如第9章所述,注入一个数字字段的攻击并不需要使用单引号。而且,如果其中包含的引号没有被转义的数据随后又从数据库中读回,那么仍可以利用这些数据实施二阶攻击。

在不需要任何转义的情况下处理数据时,激活magic quotes选项可能会使PHP对用户输入进行不必要的修改,导致代码中多出斜线,还要使用stripslashes函数删除。

在必要时,一些应用程序通过addslashes函数提交参数,自行对相关输入进行转义。如果PHP配置激活了magic quotes,那么这种方法就会导致双重转义字符,这时就会将配对的斜线解释为字面量反斜线,潜在恶意字符不会转义。

由于magic quotes选项的局限性与不规则性,建议禁用该选项,使用预处理语句安全访问数据库。

 注解 PHP 6已删除magic quotes选项。

其他

表19-10列出了其他一些可能影响PHP应用程序安全的配置选项。

表19-10 其他PHP配置选项

选 项 描 述

allow_url_fopen 如果禁用,该指令阻止一些文件函数访问远程文件(如前文所述)

allow_url_include 如果禁用,该指令阻止PHP文件包含函数用于包含一个远程文件

display_errors 如果禁用,该指令阻止PHP向用户浏览器发送错误消息。log_errors与error_log选项

可在服务器上记录错误消息,以方便诊断错误

file_uploads 如果激活,该指令将导致PHP允许通过HTTP上传文件

upload_tmp_dir 这个指令可用于指定保存上传的文件的临时目录。该指令确保不会将敏感文件保存在任何

用户都可访问的位置

Perl

本节主要介绍在Perl平台上获取用户提交的输入的方法、与用户会话交互的方式、存在的潜在危险的API以及与平台安全相关的配置选项。

众所周知,Perl语言允许开发者以各种方式执行相同的任务。而且,有大量Perl模块可满足不同的需求。如果Perl使用任何不常见的或所有权模块,应对这些模块进行仔细审查,确定它们是否使用了任何强大的或危险的函数,从而引入应用程序直接使用这些函数时引入的相同漏洞。

CGI.pm是最常用于创建Web应用程序的Perl模块,当对用Perl编写的Web应用程序进行代码审查时,很可能遇到这个模块所使用的API。

确定用户提交的数据

表19-11列出了CGI查询对象的全部成员。

表19-11 用于获取用户提交的数据的CGI查询成员

选 项 描 述

param

param_fetch 如果调用时不使用参数,param返回请求中所有参数名称的列表

如果调用时使用参数名称,param返回该请求参数的值

param_fetch方法返回一个命名参数数组

Vars 它返回参数名称与值之间的散列映射

cookie

raw_cookie 使用cookie函数可设定和检索一个命名cookie的值

raw_cookie函数返回HTTP Cookie消息头的全部内容,但不进行任何解析

self_url

url 这些函数返回当前URL,前者包含所有查询字符串

query_string 这个函数返回当前请求的查询字符串

referer 这个函数返回HTTP Referer消息头的值

request_method 这个函数返回请求中使用的HTTP方法

user_agent 这个函数返回HTTP User_agent消息头的值

http

https 这些函数返回当前请求中的所有HTTP环境变量列表

ReadParse 这个函数返回一个名为%in的数组,其中包含所有请求参数的名称与值

会话交互

Perl模块CGISession.pm对模块CGI.pm进行扩展,为会话追踪与数据存储提供支持。例如:

潜在危险的API

这一节介绍一些常见的Perl API。以危险的方式使用这些API可能会造成安全漏洞。

文件访问

Perl使用下面的API访问文件:

open

sysopen

open函数用于读取或写入指定文件的内容。如果以文件名参数提交用户可控制的数据,攻击者就可以访问服务器文件系统上的任意文件。

另外,如果文件名参数的开头或结尾为管道符(|),那么这个参数的内容被提交给一个shell命令。如果攻击者能够注入包含管道符或分号之类的shell元字符的数据,那么他们就可以执行任意命令。例如,在下面的代码中,攻击者可以注入$useraddr参数,以执行系统命令:

数据库访问

selectall_arrayref函数用于向数据库发送一个查询,并以一系列数组的形式检索查询结果。do函数用于执行一个查询,并返回受影响的行的数量。在这两个函数中,SQL语句以一个简单的字符串提交。

如果用户可控制的数据属于字符串参数的一部分,那么应用程序就可能容易受到SQL注入攻击。例如:

它会执行不良查询:

prepare与execute函数可用于创建预处理语句,允许应用程序建立一个包含参数占位符的SQL查询,并以可靠而且类型安全的方式设定这些占位符的值。如果按正常的方式使用,这种机制就不易受到SQL注入攻击。例如:

它生成的查询等同于:

动态代码执行

eval可用于动态执行包含Perl代码的字符串。分号分隔符用于将几个语句连接在一起。如果向这个函数提交用户可控制的数据,那么应用程序可能易于受到脚本注入攻击。

OS命令执行

下面这些函数可用于执行操作系统命令:

system

exec

qx

反单引号(`)

所有这些命令都可以使用 | 字符链接在一起。如果未经过滤就向这些函数提交用户可控制的数据,攻击者就可以在应用程序中执行任意命令。

URL重定向

CGI查询对象成员之一的redirect函数接受一个包含相对或绝对URL的字符串;用户被重定向到该URL。如果这个字符串的值由用户控制,那么应用程序可能易于受到钓鱼攻击。

套接字

使用socket创建一个套接字后,再通过调用connect在它与远程主机之间建立连接,connect函数接受由目标主机的IP与端口信息组成的sockaddr_in结构。如果用户能够以某种方式控制这些主机信息,攻击者就可以利用应用程序与任意主机建立网络连接,无论这些主机位于因特网上、私有DMZ中还是在应用程序上运行的内部网络内。

配置Perl环境

Perl提供一个污染模式,防止用户提交的输入被传送给潜在危险的函数。通过以下方式向Perl解释器提交-T标记,可在污染模式下执行Perl程序。

当某个程序在污染模式下运行时,解释器会追踪该程序以外提交的每一个输入,并把它当做被污染的输入处理。如果另一个变量根据一个受污染的数据分配它的值,那么Perl也认为它受到污染。例如:

不能将污染的变量提交给一系列功能强大的命令,包括eval、system、exec与open。要在敏感操作中使用污染的数据,就必须执行一项模式匹配操作并提取匹配的子字符串,“清洁”这些数据。例如:

虽然污染模式机制旨在防止许多类型的漏洞,但只有当开发者使用适当的正则表达式从被污染的输入中提取“清洁”的数据时它才会有效。如果一个表达式的范围过于宽泛,并提取了使用时可能引起问题的数据,那么污染模式提供的保护就会失效,应用程序仍然易于受到攻击。实际上,污染模式被当做一种提醒机制,它告诉程序员在危险操作中使用输入的数据前,必须对所有输入进行适当确认。它不能保证所实施的输入确认已经足够全面。

JavaScript

由于客户端JavaScript不需要任何应用程序访问权限即可访问,因此,任何时候都可以执行以安全为中心的代码审查。这类审查的关键在于确定客户端组件中的所有漏洞,如基于DOM的XSS,它们使用户易于受到攻击(请参阅第12章了解相关内容)。审查JavaScript的另一个原因是,这样做有助于了解客户端实施了哪些输入确认,以及动态生成的用户界面的结构。

当审查JavaScript代码时,必须确保检查.js文件和在HTML内容中嵌入的脚本。

需要重点审查的是那些读取基于DOM的数据以及写入或以其他方式修改当前文档的API,如表19-12所示。

表19-12 读取基于DOM数据的JavaScript API

API 描 述

document.location

document.URL

document.URLUnencoded

document.referer

window.location 这些API可用于访问通过专门设计的URL控制的DOM数据,因而攻击者

可向它们提交专门设计的数据,攻击其他应用程序用户

document.write()

document.writeln()

document.body.innerHtml

eval()

window.execScript()

window.setInterval()

window.setTimeout() 这些API可用于更新文档的内容并动态执行JavaScript代码。如果向这些

API提交攻击者可控制的数据,他就可以在受害者的浏览器中执行任意

JavaScript代码

数据库代码组件

如今,Web应用程序已不仅仅使用数据库实现数据存储。今天的数据库包含丰富的编程接口,可在数据库层执行大量的业务逻辑。开发者频繁使用数据库代码组件(如存储过程、触发器和用户定义的函数)完成各种关键任务。因此,当审查一个Web应用程序的源代码时,必须将数据库中执行的所有逻辑包括在审查范围之内。

数据库代码组件中的编程错误可能会导致本章描述的各种安全漏洞。但现实操作中应当留意两种主要的漏洞。首先,数据库组件自身可能包含SQL注入漏洞;其次,用户输入可能会以危险的方式提交给潜在危险的函数。

SQL注入

第9章介绍了如何使用预处理语句代替动态SQL语句,以防止SQL注入攻击。然而,即使在整个Web应用程序代码中正确使用预处理语句,如果数据库代码组件以危险的方式使用用户提交的输入构造查询,SQL注入漏洞也依然存在。

下面以一个@name参数易于受到SQL注入的存储过程为例:

即使应用程序将用户提交的name值安全传送给存储过程,该过程本身也会直接把这个值连接到一个动态查询中,因此它易于受到攻击。

不同的数据库平台使用不同的方法动态执行包含SQL语句的字符串,如下所示。

MS-SQL:EXEC

Oracle:EXECUTE IMMEDIATE

Sybase:EXEC

DB2:EXEC SQL

应对出现在数据库代码组件中的这些表达式进行仔细审查。如果将用户提交的输入用于构建SQL字符串,应用程序可能易于受到SQL注入攻击。

 注解 在Oracle中,存储过程默认在定义者权限而非调用者权限下运行(与UNIX中的SUID程序相同)。因此,如果应用程序使用一个低权限账户访问数据库,并且使用DBA账户建立存储过程,那么攻击者就可以利用某个过程中存在的SQL注入漏洞提升自己的权限,并执行任意数据库查询。

调用危险的函数

存储过程之类的定制代码组件常用于执行不常见的或功能强大的操作。如果以不安全的方式向一个潜在危险的函数传送用户提交的数据,那么根据该函数的功能,这样做可能会导致各种类型的漏洞。例如,下面的存储过程的@loadfile与@loaddir参数就易于受到命令注入攻击。

如果以不安全的方式调用,下面的函数可能造成危险。

MS-SQL与Sybase中功能强大的存储过程,使用它们可执行命令或访问注册表等。

用于访问文件系统的函数。

连接到数据库以外的库的用户定义的函数。

可访问网络的函数;例如,通过MS-SQL中的OpenRowSet或Oracle中的数据库链接。

代码浏览工具

到目前为止,我们描述的代码审查方法大多要求阅读源代码,并从中搜索表示获取用户输入及使用潜在危险的API的模式。因此,为进行有效的代码审查,最好使用一款智能工具浏览代码;也就是说,该工具能够理解各种语言使用的代码结构,提供与特定API和表达式有关的上下文信息,并能够方便地进行导航。

在许多语言中,可以使用某种开发工作室,如Visual Studio、NetBeans或Eclipse。还有各种一般性的代码浏览工具,它们支持各种语言,并且可进行优化,以方便阅读代码。Source Insight是我们首选的工具,如图19-1所示。它支持源代码树浏览,拥有强大的搜索功能,使用一个预览框显示与任何选中的表达式有关的上下文信息,并且能够在代码之间快速导航。

图19-1 使用SourceInsight搜索和浏览某个Web应用程序的源代码

小结

许多在测试Web应用程序方面拥有实际经验的人,对于审查一个应用程序的代码并直接从中发现漏洞,往往表现出不合常理的恐惧。对于那些没做过程序员的人而言,产生这种恐惧是可以理解的,但这种表现并没有合理的根据。任何熟悉计算机的人,只要花一点儿投资,就可以拥有足够的知识与信心,进行有效的代码审查。当审查一个应用程序的代码时,不一定要发现其中包含的“全部”漏洞,任何人在亲手进行测试时都不会设定这个不现实的目标。更合理的做法是,着手了解应用程序对用户提交的输入进行了哪些关键的处理,认清一些表示应用程序可能存在漏洞的签名。这样,代码审查才可以与大家更加熟悉的“黑盒”测试方法互为补充,提高“黑盒”测试的效率,并披露完全从外部访问应用程序时非常难以发现的漏洞。

问题

欲知问题答案,请访问http://mdsec.net/wahh。

(1)列出3种可在源代码中找到明确签名的常见漏洞。

(2)当审查PHP应用程序时,为什么有时很难确定用户输入的所有来源?

(3)以下两个执行SQL查询的方法都使用了用户提交的输入:

哪一个方法更加安全,为什么?

(4)在审查一个Java应用程序代码时,首先要检查HttpServletRequest.getParameter API的所有用法。下列代码引起了你的注意:

这段代码表示应用程序中可能存在什么漏洞?还需要进行哪些代码分析才能确定应用程序是否确实易于受到攻击?

(5)假设渗透测试员正在审查一个应用程序用于生成会话令牌的机制。相关代码如下:

应用程序生成的会话令牌是否可以预测?请解释理由。

浙ICP备11005866号-12