3.2.1 受影响的编程语言
1.按编程语言分类
(1)注入解释型语言
解释型语言(interpreted language)是一种在运行时由一个运行时组件(runtime component)解释语言代码并执行其中包含指令的语言。与之相对,编译型语言(compiled language)是这样一种语言:它的代码在生成时转换成机器指令,然后在运行时直接由使用该语言的计算机处理器执行这些指令。
从理论上说,任何语言都可使用编译器或解释器来执行,这种区别并不是语言本身的内在特性。但是,通常大多数语言仅通过上述其中一种方式执行,开发Web应用程序使用的许多核心语言使用解释器执行,包括SQL、LDAP、Perl和PHP。
基于解释型语言的执行方式,产生了一系列叫作代码注入(code injection)的漏洞。任何有实际用途的应用程序都会收到用户提交的数据,对其进行处理并执行相应的操作。因此,由解释器处理的数据实际上是由程序员编写的代码和用户提交的数据共同组成的。有些时候,攻击者可以提交专门设计的输入,通常提交某个在应用程序中使用解释型语言语法的具有特殊意义的句法,向应用程序实施攻击。结果,这个输入的一部分被解释成程序指令执行。好像它们是由最初的程序员编写的代码一样。因此,如果这种攻击取得成功,它将完全攻破目标应用程序的组件。
注入代码的方法通常并不利用开发目标程序所使用语言的任何语法特性,注入的有效载荷为机器代码,而不是用那种语言编写的指令。
(2)注入操作系统命令
大多数Web服务器平台发展迅速,现在它们已能够使用内置的API与服务器的操作系统进行几乎任何必需的交互。如果正确使用,这些API可帮助开发者访问文件系统、连接其他进程、进行安全的网络通信。但是,许多时候,开发者选择使用更高级的技术直接向服务器发送操作系统命令。由于这些技术功能强大、操作简单,并且通常能够立即解决特定的问题,因而具有很强的吸引力。但是,如果应用程序向操作系统命令传送用户提交的输入,那么就很可能会受到命令注入攻击,由此攻击者能够提交专门设计的输入,修改开发者想要执行的命令。
常用于发出操作系统命令的函数,如PHP中的exec()和ASP中的wscript.shell()函数,通常并不限制命令的可执行范围。即使开发者准备使用API执行相对善意的任务,如列出目录的内容,攻击者还是可以对其进行暗中破坏,从而写入任意文件或启动其他程序。通常,所有的注入命令都可在Web服务器的进程中安全运行,它具有足够强大的功能,使得攻击者能够完全控制整个服务器。
许多非定制和定制Web应用程序中都存在这种命令注入缺陷。在为企业服务器或防火墙、打印机和路由器之类的设备提供管理界面的应用程序中,这类缺陷尤其普遍。通常,因为操作系统交互允许开发者使用合并用户提交的数据的直接命令,所以这些应用程序都对交互过程提出了特殊的要求。
(3)攻击本地编译型应用程序
过去,在本地执行环境中运行的编译型软件一直受到缓冲区溢出与格式化字符串(format string)等漏洞的困扰。如今,绝大多数的Web应用程序都是使用在托管执行环境中运行的语言和平台编写的,这个环境中不存在上述典型漏洞。使用C#和Java这类语言的一个主要优点在于,程序员不必再担心缓冲区管理与指针算法等问题。这些问题曾给以本地语言(如C和C++)开发的软件造成极大影响,并且是这些软件中绝大多数严重漏洞的根源所在。
但是,有时也会遇到用本地代码编写的Web应用程序。而且,许多主要使用托管代码编写的应用程序同样包含本地代码或调用在非托管环境中运行的外部组件。除非渗透测试员确切地知道所针对的应用程序并不包含任何本地代码,否则就有必要对它进行一些基本的检查,查明其中是否存在任何常见的漏洞。
在打印机与交换机等硬件设备上运行的Web应用程序常常使用某种本地代码。其他可能的目标包含:任何其名称(如dll或exe)表示它使用了本地代码的页面或脚本,以及任何已知调用遗留外部组件的功能(如日志机制)。如果认为所攻击的应用程序包含大量的本地代码,那么就有必要对应用程序处理的每个用户提交的数据进行测试.包括每个参数的名称与参数值、cookie、请求消息头及其他数据。
2.按源码类型分类
(1)Java
这一节介绍一些常见的Java API。以危险的方式使用这些API可能会造成安全漏洞。
1)文件访问
在Java中,用于访问文件与目录的主要的类为java.io.File。从安全的角度看,最重要的用法是调用它的构造函数,该构造函数接受一个父目录和文件名,或者一个路径名。但无论以哪种方式使用构造函数,如果未检查其中是否包含“..\\”序列就将用户可控制的数据作为文件名参数提交,那么可能会造成路径遍历漏洞。例如,下面的代码将打开Windows C:\驱动器根目录下的一个文件:
在Java中,常用于读取与写入文件内容的类包括:
这些类从它们的构造函数中提取File对象,文件名字符串打开文件,如果使用用户可控制的数据作为这个参数提交,同样可能会引入路径遍历漏洞。例如:
2)数据库访问
下面这些是常用于以SQL查询任何一个字符串的API:
如果用户提交的数据属于以查询执行的字符串的一部分,那么它可能易受到SQL注入攻击,如下代码:
最后一条语句如果实际上执行了如下不良查询,则可能产生了SQL注入攻击。
下面的API更加稳定可靠,能够替代前面描述的API,允许应用程序创建一个预先编译的SQL语句,并以可靠且类型安全的方式指定它的参数占位符的值:
当然还有许多,此处不一一列出。如果按正常的方式使用,这些API就不易受到SQL注入攻击,查询语句:
结果等同于:
3)动态代码执行
Java语言本身并不包含任何动态评估Java源代码的机制,尽管一些应用(主要在数据库产品中)提供了评估方法。如果所审查的应用程序动态构建任何Java代码,就应该了解应用程序如何构建这些代码,并决定用户可控制的数据是否以危险的方式使用。
4)OS命令执行
下面的API用于在Java应用程序中执行外部操作系统命令:
如果提交给exec()的字符串参数可完全由用户控制,那么几乎可以肯定应用程序易于受到任何命令执行攻击。
例如,下面的代码将运行Windows calc程序:
然而,如果用户仅能够控制提交给exec()的部分字符串,那么应用程序可能不易于受到攻击。在下面的示例中,用户可控制的数据以命令行参数的形式提交给记事本进程,引起它尝试加载|calc文档:
exec API本身并不解释“&”与“|”等shell元字符,因此这个攻击失败。
有时,仅控制部分字符串提交给exec()仍然足以执行任意命令。例如下面这个稍微不同的示例(注意notepad后面缺少一个空格):
通常,在这种情况下,应用程序将易于受到除代码执行以外的攻击。例如,如果应用程序以用户可控制的参数作为目标URL执行wget程序,那么攻击者就可以向wget进程传递危险的命令行参数,例如,致使它下载一个文档,并将该文档保存在文件系统中的任何位置。
5)URL重定向
下面的API用于在Java中发布HTTP重定向:
通常,使用send Redirect方法可以引起一个重定向响应,该方法接受一个包含相对或绝对URL的字符串。如果这个字符串的值由用户控制,那么应用程序可能易于受到钓鱼攻击。
另外,还应该审查setStatus与add Header API的所有用法,如果某个重定向包含一个含有HTTP Location消息头的3xx响应,应用程序就可能使用这些API执行重定向。
6)套接字
java.net.socket类从它的构造函数中提取与目标主机和端口有关的各种信息,如果用户能够以某种方式控制这些信息,攻击者就可以利用应用程序与任意主机建立网络连接,无论主机位于互联网上、私有DMZ中还是在应用程序上运行的内部网络内。
(2)ASP.NET
这一节介绍一些常见的ASP.NET API。以危险的方式使用这些API可能会造成安全漏洞。
1)文件访问
System.IO.File是用于访问ASP.NET文件最主要的类。它的所有方法都是静态的,并且没有公共构造函数。
这个类的37个方法全都接受一个文件名作为参数。如果未检查其中是否包含“..\\”序,就提交用户可控制的数据,就会造成路径遍历漏洞。例如,下面的代码将打开Windows C:\驱动器根目录下的一个文件:
下面的类常用于读取与写入文件内容:System.IO.FileStream,System.IO.SteamReader,System.IO.StreamWriter。
它们的各种构造函数接受一个文件路径作为参数。如果提交用户可控制的数据,这些构造函数可能引入路径遍历漏洞。例如:
2)数据库访问
ASP.NET有许多用于访问数据库的API,下面的类主要用于建立并执行SQL语句:
其中每个类都有一个构造函数,它接受一个包含SQL语句的字符串,而且每个类都有一个command Text属性,可用于获取并设定SQL语句的当前值。如果适当地配置一个命令对象,通过调用Execute方法即可执行SQL语句。
它生成的查询等同于:
3)动态代码执行
VBScript函数Eval()接受一个包含VBScript表达式的字符串自变量。该函数求出这个表达式的值,并返回结果。如果用户可控制的数据被合并到要计算值的表达式中,那么用户就可以执行任意命令或修改应用程序的逻辑。
函数Execute()和ExecuteGlobal()接受一个包含ASP代码的字符串,这个ASP代码与直接出现在脚本的代码的执行方式完全相同。冒号分隔符将用于将几个语句连接在一起。如果向Execute()函数提交用户可控制的数据,那么攻击者就可以在应用程序中执行任意命令。
4)OS命令执行
下面的API可以用各种方式在ASP.NET应用程序中运行外部进程:
在对对象调用start之前,可以向静态Process.Star方法提交一个文件名字符串,或者用一个文件名配置Process对象的StartInfo属性。如果文件名字符串可完全由用户控制,那么应用程序几乎可以肯定易于受到任意命令执行攻击。
5)URL重定向
下面的API用于在ASP.NET中发布一个HTTP重定向:
通常,使用Http Response.Redirect方法可以引起一个重定向响应,该方法接受一个包含相对或绝对URL的字符串。如果这个字符串的值由用户控制,那么应用程序可能易于受到钓鱼攻击。
另外,还必须确保检查Status/StatusCode属性与Add Header/Append Header方法的用法。如果某个重定向包含一个含有HTTP Location消息头的3xx响应,应用程序就可能使用这些API执行重定向。
Server.Transfer方法有时也可用于实现重定向。实际上,这个方法并不能实现HTTP重定向,而是应根据当前请求修改被服务器处理的页面。因此,不能通过破坏它重定向到一个站外URL。这个方法对攻击者而言并没有多大用处。
6)套接字
System.Net.Sockets.Socket类用于创建网络套接字。创建一个Socket对象后,再通过调用Connect方法连接这个对象。该方法接受目标主机的IP与端口信息为参数。如果用户能够以某种方式控制这些主机信息,攻击者就可以利用应用程序与任意主机建立网络连接,无论这些主机位于互联网上、私有DMZ中还是在应用程序上运行的内部网络内。
(3)PHP
这一节介绍一些常见的PHP API。以危险的方式调用这些API可能会造成安全漏洞。
1)文件访问
PHP中包含大量用于访问文件的函数,其中许多接受可用于访问远程文件的URL和其他结构。下面的函数用于读取或写入一个指定文件的内容。如果向这些API提交用户可控制的数据,攻击者就可以利用这些API访问服务器文件系统上的任意文件。
下面的函数用于包含并执行一个指定的PHP脚本。如果攻击者能够使应用程序执行受控的文件,他就可以在服务器上执行任意命令。
如果攻击者可向服务器上传任意文件,他仍然能够执行任意命令。PHP配置选项allow_url_fopen可用于防止一些远程文件。但是,在默认情况下,这个选项设为1(表示允许远程文件),因此表3.2中列出的协议可用于检索远程文件。
表3.2 可用于检索远程文件的协议
即使allow_url_fopen设为0,攻击者仍然可以使用。
表3.3列出的方法实现访问远程文件(取决于所安装的扩展)。
表3.3 allow_url_fopen为0时仍然可用于访问远程文件的方法
2)数据库访问
下面的函数用于向数据库发送一个查询并检查查询结果:
SQL语句以一个简单的字符串提交。如果用户可控制的数据属于字符串参数的一部分,那么应用程序就可能容易受到SQL注入攻击。例如:
它会执行不良查询:
下面的函数可用于创建预处理语句,允许应用程序建立一个包含参数占位符的SQL查询,并以可靠而且类型安全的方式设定这些占位符的值:
反单引号
如果所有这些命令都可以使用数据,那么攻击者就可以使用“|”将字符链接在一起。如果未经过滤就向这些函数提交用户可控制的数据,那么攻击者就可以在应用程序中执行任意命令。
3)URL重定向
下面的API用于在PHP中发布一个HTTP重定向:
通常,使用http_redirect()函数可以实现一个重定向,该函数接受一个包含相对或绝对URL的字符串。如果这个字符串的值由用户控制,那么应用程序可能易于受到钓鱼攻击。通过调用包含适当Location消息头的header()函数也可以实现重定向,它让PHP得出结论,认为需要一个HTTP重定向。例如:
另外,我们还应仔细审查set ResponseCode与set Headers API的用法。如果某个重定向包含一个含有HTTPLocation消息头的3xx响应,应用程序就可能使用这些API执行重定向。
4)套接字
下面的API用于在PHP中建立和使用网络套接字:
使用socket_create创建一个套接字后,再通过调用socket_connect与远程主机建立连接。这个API接受目标主机的IP与端口信息为参数。如果用户能够以某种方式控制这些主机信息,攻击者就可以利用应用程序与任意主机建立网络连接,无论这些主机位于公共互联网上、私有DMZ中还是应用程序运行的内部网络中。
fsockeopen()与pfsockopen()函数可用于打开连接指定主机与端口的套接字,并返回一个可用在fwrite()和fgets()等标准文件函数中的文件指针。如果向这些函数提交用户数据,应用程序就可能易于受到攻击,如前文所述。
(4)JavaScript
由于客户端JavaScript不需要任何应用程序访问权限即可访问,因此,任何时候都可以执行以安全为中心的代码审查。这类审查的关键在于确定客户端组件中的所有漏洞,如基于DOM的XSS,它们使用户易于受到攻击。审查JavaScript的另一个原因是,有助于了解客户端实施了哪些输入确认,以及动态生成的用户界面的结构。
当审查JavaScript代码时,必须确保检查.js文件和在HTML内容中嵌入的脚本。需要重点审查的是那些基于DOM的数据的读取以及写入,或以写入及其他方式修改当前文档的API,如表3.4所示。
表3.4 读取基于DOM数据的JavaScript API