测试后端组件

Web应用程序正变得日益复杂。它们常常作为一系列后端业务关键资源,包括网络资源(如Web服务、后端Web服务器、邮件服务器)和本地资源(如文件系统)面向因特网的接口及操作系统接口。而且,应用程序服务器还作为这些后端组件的自主访问控制层。任何能够与后端组件进行任意交互的攻击都将能够突破Web应用程序实施的整个访问控制模型,从而以未授权方式访问敏感数据和功能。

数据在不同组件间传递时,它们将由不同类型的API和接口解释。被核心应用程序视为“安全”的数据,在支持不同编码、转义字符、字段分隔符或字符串终止符的上层组件看来可能极不安全。此外,上层组件可能会有相对多的功能是应用程序在正常情况下所不会调用的。因此,利用注入漏洞的攻击者通常不仅能够突破应用程序的访问控制,甚至能够利用后端组件支持的其他功能来攻破组件基础架构的关键部分。

注入操作系统命令

大多数Web服务器平台发展迅速,现在它们已能够使用内置的API与服务器的操作系统进行几乎任何必需的交互。如果正确使用,这些API可帮助开发者访问文件系统、连接其他进程、进行安全的网络通信。但是,许多时候,开发者选择使用更高级的技术直接向服务器发送操作系统命令。由于这些技术功能强大、操作简单,并且通常能够立即解决特定的问题,因而具有很强的吸引力。但是,如果应用程序向操作系统命令传送用户提交的输入,那么就很可能会受到命令注入攻击,由此攻击者能够提交专门设计的输入,修改开发者想要执行的命令。

常用于发出操作系统命令的函数,如PHP中的exec和ASP中的wscript.shell函数,通常并不限制命令的可执行范围。即使开发者准备使用API执行相对善意的任务,如列出目录的内容,攻击者还是可以对其进行暗中破坏,从而写入任意文件或启动其他程序。通常,所有的注入命令都可在Web服务器的进程中安全运行,它具有足够强大的功能,使得攻击者能够完全控制整个服务器。

许多非定制和定制Web应用程序中都存在这种命令注入缺陷。在为企业服务器或防火墙、打印机和路由器之类的设备提供管理界面的应用程序中,这类缺陷尤其普遍。通常,因为操作系统交互允许开发者使用合并用户提交的数据的直接命令,所以这些应用程序都对交互过程提出了特殊的要求。

例1:通过Perl注入

以下面的Perl CGI代码为例,它是一个用于服务器管理的Web应用程序代码的一部分。这项功能允许管理员在服务器上指定一个目录,并查看它的磁盘使用情况:

如果按设想的方式运行,这段脚本将把用户提交的dir参数值附加在预先设定的命令后面,执行命令并显示结果,如图10-1所示。

图10-1 一个列出目录内容的简单应用程序功能

然而,通过提交专门设计的、包含shell元字符的输入,攻击者可对这项功能进行各种方式的利用。对处理命令的解释器而言,这些字符有着特殊的含义,并可破坏开发者想要执行的命令。例如,管道符“|”可用于将一个进程的输出重定向为另一个进程的输入,从而将几个命令连接在一起。攻击者可以利用这种行为注入另外一个命令并获得输出结果,如图10-2所示。

图10-2 一个成功的命令注入攻击

在这个攻击中,最初的du命令的输出已被重定向为cat/etc/passwd命令的输入。不过,这个命令不会理睬上述输入,而是执行自己的任务:输出passwd文件的内容。

这样简单的攻击似乎是不可能的;但是,实际上在众多商业产品中都已发现这类命令注入漏洞。例如,已发现下面URL中的HP Openview存在一个易受攻击的命令注入缺陷:

例2:通过ASP注入

以下面的C#代码为例,它是一个用于管理Web服务器的Web应用程序代码的一部分。该功能允许管理员查看被请求的目录的内容:

如果按设想的方式运行,这段脚本将把用户提交的Directory参数值插入到预先设定的命令中,执行命令并显示结果,如图10-3所示。

图10-3 一个列出目录内容的功能

和前面易受攻击的Perl脚本一样,攻击者可以使用shell元字符破坏开发者预先设定的命令,并注入他自己的命令。&字符用于将几个命令组合在一起。提交一个包含&字符的文件名和另外一个命令就可以执行该命令并显示其结果,如图10-4所示。

图10-4 一个成功的命令注入攻击

尝试访问

http://mdsec.net/admin/5/

http://mdsec.net/admin/9/

http://mdsec.net/admin/14/

通过动态执行注入

许多Web脚本语言支持动态执行在运行时生成的代码。这种特性允许开发者创建可根据各种数据和条件动态修改其代码的应用程序。如果用户输入合并到可动态执行的代码中,那么攻击者就可以提交专门设计的输入,破坏原有的数据,指定服务器执行自己的命令,就好像这些命令是由最初的开发者编写的一样。这时,攻击者的第一个目标通常是注入运行操作系统命令的API。

PHP函数eval可用于动态执行在运行时传送给该函数的代码。下面以一个搜索功能为例,该功能允许用户创建保存的搜索,然后在用户界面上以链接的形式动态生成这些搜索。用户使用下面的URL访问该搜索功能:

服务器端应用程序通过动态生成变量来执行这项功能,生成的变量包含在storedsearch参数中指定的名/值对;此处,它创建值为wahh的变量mysearch。

这时,就可以提交专门设计的输入,由eval函数动态执行,从而在服务器端应用程序中注入任意PHP命令。分号字符可用于在单独一个参数中将几个命令连接在一起。例如,要检索文件/etc/password的内容,可以使用file_get_contents或system命令:

 注解 Perl语言也包含一个可通过同样的方式加以利用的eval函数。注意,可能需要对分号字符进行URL编码(为%3b),因为一些CGI脚本解析器将它解释为参数分隔符。在传统的ASP中,Execute()执行类似的任务。

查找OS命令注入漏洞

在应用程序解析过程中(请参阅第4章了解有关内容),通过调用外部进程或访问文件系统,应该能确定任何Web应用程序与基础操作系统交互的情形。攻击者开始攻击前要探查所有这些功能,寻找命令注入漏洞。然而,实际上,应用程序发出的操作系统命令中可能包含用户提交的任何数据项,包括每个URL、请求主体参数及cookie。因此,为对应用程序进行全面彻底的测试,必须检查每项应用程序功能中的所有这些数据项。

不同的命令解释器处理shell元字符的方式各不相同。理论上,任何类型的应用程序开发平台或Web服务器可能会调用任何shell解释器,在它自己或任何其他主机的操作系统上运行。因此,不应根据对Web服务器操作系统的了解,对应用程序如何处理元字符做出任何假设。

有两种类型的元字符可用于在一个现有的预先设定的命令中注入一个独立的命令。

字符;|&和换行符可用于将几个命令逐个连接在一起。有些时候,可以成对使用这些字符以达到不同的效果。例如,在Windows命令解释器中,使用&&则第二个命令只有在第一个命令成功执行后才会运行。使用||则总运行第二个命令,无论第一个命令是否成功执行。

和本章开头的示例一样,反引号(`)用于将一个独立的命令包含在最初的命令处理的数据中。把一个注入的命令放在反引号内shell解释器就会执行该命令,并用这个命令的结果代替被包含的文本,然后继续执行得到的命令字符串。

在前面的示例中,可以直接确定是否可以注入命令,并获得注入命令的执行结果,因为应用程序会在响应中立即返回那些结果。然而,在许多情况下,这样做是不可能的。可以注入一个命令,但它不会返回结果,也不会以任何可确定的方式影响应用程序随后的处理过程。或者由于几个命令连接在一起,注入命令的执行结果在执行过程中丢失了。

通常,检测命令注入是否可行的最可靠方法是使用时间延迟推断,类似于前面描述的利用盲目SQL注入时使用的方法。如果一个潜在的漏洞可能存在,那么就可以使用其他方法确定这个漏洞,并获得注入命令的执行结果。

渗透测试步骤

(1)通常可以使用ping命令让服务器在一段时期内检测它的回环接口(loopback interface),从而触发时间延迟。Windows和UNIX平台在处理命令分隔符与ping命令方面存在一些细微的差别,但是,如果没有设置过滤,下面的通用测试字符串应该能够在两个平台上引起30秒的时间延迟。

如果应用程序过滤掉某些命令分隔符,为加大检测到命令注入漏洞的可能性,还应该轮流向每一个目标参数提交下面的每个测试字符串,并监控应用程序进行响应的时间。

(2)如果发生时间延迟,说明应用程序可能易于受到命令注入攻击。重复几次测试过程,确定延迟不是由于网络延时或其他异常造成的。可以尝试更改–n或–i参数的值,并确定经历的时间延迟是否会随着提交的值发生对应的变化。

(3)使用所发现的任何一个可成功实施攻击的注入字符串,尝试注入另一个更有用的命令(如ls或dir),确定是否能够将命令结果返回到浏览器上。

(4)如果不能直接获得命令执行结果,还可以采用其他方法。

可以尝试打开一条通向自己计算机的带外通道。尝试使用TFTP上传工具至服务器,使用telnet或netcat建立一个通向自己计算机的反向shell,并使用mail命令通过SMTP发送命令结果。

可以将命令结果重定向到Web根目录下的一个文件,然后使用浏览器直接获取结果。例如:

(5)一旦找到注入命令的方法并能够获得命令执行结果,就应当确定自己的权限(通过使用whoami或类似命令,或者尝试向一个受保护的目录写入一个无害的文件)。然后就可以设法提升自己的权限,进而秘密访问应用程序中的敏感数据,或者通过被攻破的服务器攻击其他主机。

有时,由于某些字符被过滤掉,或者应用程序所使用的命令API的特殊行为,可能无法注入一个完全独立的命令。但是,攻击者仍然可以破坏所执行的命令的行为,得到想要的结果。

笔者曾遇到这样的情况:应用程序向操作系统命令nslookup传递用户输入,以查找用户提交的域名的IP地址。注入新命令所需的元字符被阻止,但允许使用<和>字符重定向命令的输入和输出。nslookup命令通常输入某个域名的IP地址,这似乎并未提供任何有效的攻击向量。但是,如果在这时提交一个无效域名,该命令就会输出错误消息,并在其中包含所查询的域名。通常,这种行为足以导致严重的攻击。

提供一个服务器可执行的脚本代码片段,以替代要解析的域名。可以将这段脚本放在引号中,以确保命令解释器将其视为一个令牌。

使用>字符将命令的输出重定向到Web根目录下的可执行文件夹中的某个文件。由操作系统执行的命令如下所示:

运行以上命令时,会将以下输出重定向到可执行文件:

然后,使用浏览器调用该文件,注入的脚本代码将在服务器上执行。由于大多数脚本语言允许页面同时包含客户端内容和服务器端标记,攻击者无法控制的错误消息部分将被视为明文,并且会执行注入脚本中的标记。因此,攻击者就可以通过利用受限制的命令注入条件,在应用程序服务器中插入一个不受限制的后门。

尝试访问

http://mdsec.net/admin/18/

渗透测试步骤

(1)<和>字符分别用于将一个文件的内容指向命令的输入以及将命令的输出指向一个文件。如果不可能使用前面的技巧注入一个完全独立的命令,仍然可以使用<和>字符读取及写入任意文件的内容。

(2)应用程序调用的许多操作系统命令接受大量控制其行为的命令行参数。通常,用户提交的输入以这种参数的形式传送给命令处理,只需在相关参数后插入一个空格,就可以在空格后添加另外一个参数。例如,一个Web创作应用程序可能拥有一项功能,允许服务器获得一个用户指定的URL,然后将它的内容呈现在浏览器上进行编辑。如果应用程序调用wget程序,那么就可以通过附加wget使用的–O命令行参数,在服务器的文件系统中写入任何文件的内容。例如:

 提示 许多命令注入攻击要求注入空格以分隔命令行自变量。如果攻击者发现应用程序过滤空格,并且攻击的是UNIX平台,那么他可以使用包含空白符字段分隔符的$IFS环境变量代替空格。

查找动态执行漏洞

动态执行漏洞最常见于PHP和Perl等语言。但基本上,任何应用程序平台都可能会向基于脚本的解释器(有时位于其他后端服务器上)传送用户提交的输入。

渗透测试步骤

(1)用户提交的所有数据项都可提交给动态执行函数。其中最常见的数据项是cookie参数名称和参数值,以及作为前一项操作结果保存在用户资料中的永久数据。

(2)尝试轮流向目标参数提交下列值:

(3)监控应用程序的响应。如果字符串111111被单独返回(即它前面没有其他命令字符串),就表示应用程序可能易于受到脚本命令注入。

(4)如果字符串111111并未返回,寻找任何表示输入被动态执行的错误消息;另外,可能需要对语法进行调整,以实现注入任意命令的目的。

(5)如果攻击的应用程序使用PHP,可以使用测试字符串phpinfo()。如果它成功执行,应用程序将返回PHP环境的配置信息。

(6)如果应用程序可能易于受到攻击,与前面描述的查找OS命令注入漏洞时一样,注入一些造成时间延迟的命令确认这一点。例如:

防止OS命令注入

通常来说,防止OS命令注入漏洞的最佳方法是完全避免直接调用操作系统命令。几乎Web应用程序所需要执行的每个任务都可以使用内置API完成,而且攻击者无法控制这些API,使其执行其他预料之外的命令。

如果无法避免要在传送给操作系统命令解释器的命令字符串中插入用户提交的数据,应用程序应实施严格的防御来防止漏洞发生。如果可能,应使用一份“白名单”限制用户只输入一组特殊的值。或者,应将输入范围限制为少数字符,例如,仅字母数字字符。应拒绝包含任何其他数据(包括任何元字符或空白符)的输入。

应用程序应使用命令API通过它的名称和命令行参数启动特殊的进程,而不是向支持命令链接与重定向的shell解释器传送命令字符串,从而实施另一层保护。例如,Java API Runtime.exec和ASP.NET API Process.Start并不支持shell元字符,如果使用得当,它们能够确保仅执行开发者想要执行的命令。请参阅第19章了解与命令执行API有关的详情。

防止脚本注入漏洞

通常而言,防止脚本注入漏洞的最佳方法是,避免将用户提交的输入或者来自用户的数据传送给任何动态执行或包含函数。如果由于某种原因必须传送用户提交的输入,那么应对相关输入进行严格的确认检查以阻止任何攻击。如有可能,使用一份由已知可靠的值组成的“白名单”,并拒绝任何没有出现在这个名单上的输入。如果无法做到这一点,应根据一组已知无害的字符[如字母数字字符(空白符除外)]检查在输入中使用的字符。

操作文件路径

Web应用程序中的许多功能通常都需要处理用户以文件或目录名提交的输入。一般情况下,这些输入会传递给接受文件路径的API(例如,用于检索本地文件系统中的文件)。应用程序将在它对用户请求的响应中处理该API调用的结果。如果用户提交的输入未经过正确确认,这种行为就可能导致各种安全漏洞,其中最常见的是文件路径遍历漏洞和文件包含漏洞。

路径遍历漏洞

如果应用程序使用用户可控制的数据、以危险的方式访问位于应用程序服务器或其他后端文件系统中的文件和目录,就会出现路径遍历漏洞。通过提交专门设计的输入,攻击者就可以在被访问的文件系统中读取或者写入任意内容。这种漏洞往往使攻击者能够从服务器上读取敏感信息或者重写敏感文件,并最终在服务器上执行任何命令。

在下面的示例中,应用程序使用一个动态页面向客户端返回静态图像。被请求图像的名称在查询字符串参数中指定:

当服务器处理这个请求时,它执行以下操作。

(1)从查询字符串中提取filename参数值。

(2)将这个值附加在C:\filestore\之后。

(3)用这个名称打开文件。

(4)读取文件的内容并将其返回给客户端。

漏洞之所以会发生,是因为攻击者可以将路径遍历序列(path traversal sequence)放入文件名内,从第(2)步指定的图像目录向上回溯,从而访问服务器上的任何文件。众所周知,路径遍历序列表示为“点—点—斜线”(..\),一个典型的攻击如下:

如果应用程序把filename参数的值附加到图像目录名称之后,就得到以下路径:

这两个遍历序列立即从图像目录回溯到C:驱动器根目录下,因此前面的路径等同于以下路径:

因此,服务器不会返回图像文件,而是返回默认的Windows配置文件。

 注解 在旧版Windows IIS Web服务器中,默认情况下,应用程序将在本地系统权限下运行,因而能够访问本地文件系统上的任何可读文件。在较新的版本中,和许多其他Web服务器一样,服务器进程默认以权限较低的用户账户运行。因此,在探查路径遍历漏洞时,最好是请求一个可由任何类型的用户读取的默认文件,如c:\windows\win.ini。

在这个简单的示例中,应用程序并未实施防御,阻止路径遍历攻击。然而,由于这些攻击早已广为人知,应用程序通常会针对它们实施各种防御,大多数情况下是采取输入确认过滤。在后文中会讲到,这些过滤往往并不可靠,可被技术熟练的攻击者轻易避开。

尝试访问

http://mdsec.net/filestore/8/

查找和利用路径遍历漏洞

许多功能都要求Web应用程序根据用户在请求中提交的参数向文件系统读取或写入数据。如果以不安全的方式执行这些操作,攻击者就可以提交专门设计的输入,使应用程序访问开发者并不希望它访问的文件。这类漏洞称为路径遍历(path traversal)漏洞,攻击者可利用这种漏洞读取密码和应用程序日志之类的敏感数据,或者重写安全性至关重要的数据项,如配置文件和软件代码。在最为严重的情况下,这种漏洞可使攻击者能够完全攻破应用程序与基础操作系统。

有时,路径遍历漏洞很难发现,并且许多Web应用程序对它们实施的防御也十分脆弱。下面我们将介绍攻击者在确定潜在的目标、探查易受攻击的行为、避开应用程序防御以及处理定制编码的过程中所采用的各种技巧。

确定攻击目标

在对应用程序进行初步解析的过程中,应该已经确定了所有与路径遍历漏洞有关的明显受攻击面。主要用于文件上传或下载目的的所有功能都应进行全面测试。用户可共享文档的工作流程应用程序,允许用户上传图像的博客与拍卖应用程序,以及为用户提供电子书、技术手册和公司报表等文档的信息型应用程序,常常使用这种功能。

除这种明显的目标功能外,还有其他各种行为表示应用程序需要与文件系统进行交互。

渗透测试步骤

(1)分析在应用程序解析过程中收集到的信息,确定以下内容。

请求参数中明显包含文件或目录名称的所有情形。例如,include=main.inc或template= /en/sidebar。

需要从服务器文件系统(相对于后端数据库)读取数据的所有应用程序功能。例如,显示办公文档或图像。

(2)在测试其他漏洞的过程中,寻找有益的错误消息或其他反常事件。设法确定用户提交的数据被传送给文件API或作为操作系统命令参数的所有情况。

 提示 如果攻击者已经从本地访问应用程序[通过了白盒测试(white-box testing),或者因为已经攻破了服务器的操作系统],那么往往能够直接确定路径遍历目标,因为他可以监控应用程序与文件系统之间的全部交互。

渗透测试步骤

如果能够从本地访问应用程序,执行以下操作。

(1)使用适当的工具监控服务器上的所有文件系统活动。例如,可以在Windows平台上使用SysInternals开发的FileMon工具,在Linux平台上使用ltrace/strace工具,在Sun Solaris平台上使用truss命令。

(2)在每一个被提交的参数(包括全部cookie、查询字符串字段和POST数据项)中插入一个特殊的字符串(如traversaltest)测试应用程序的每一个页面。一次仅针对一个参数进行测试,并使用第14章描述的自动技巧加速测试过程。

(3)在文件系统监控工具中设置一个过滤器,确定所有包含测试字符串的文件系统事件。

(4)如果发现测试字符串被用作文件或目录,或者出现在文件或目录名中,那么对每一种情况进行测试(如下所述),确定其是否易于受到路径遍历攻击。

探查路径遍历漏洞

确定各种潜在的路径遍历测试目标后,必须分别测试每种情况,弄清其是否以不安全的方式向相关文件系统操作传送用户可控制的数据。

在测试用户提交的参数时,需确定遍历序列是否被应用程序阻止,或者它们是否能够正常工作。通常,提交不会向上回溯到起始目录的遍历序列是一种较为可靠的初步测试方法。

渗透测试步骤

(1)假设所针对的参数被附加到应用程序预先设定的目录之后,那么插入任意一个子目录和一个遍历序列,修改参数的值。例如,如果应用程序提交参数

那么可以尝试提交以下值:

如果两种情况下应用程序的行为完全相同,就表示它易于受到攻击。应该继续进行测试,尝试通过向上回溯到起始目录来访问不同的文件。

(2)在上述两种情况下,如果应用程序的行为有所不同,那么应用程序可能阻止、删除或净化遍历序列,致使文件路径失效。需要研究是否可通过其他方法避开应用程序的确认过滤(请参阅下面讨论的内容)。

即使子目录“bar”并不存在,这个测试仍然有效,因为大多数文件系统在尝试获取文件路径前对其进行了规范化。路径序列删除了虚构的目录,因此服务器并不检查它是否存在。

如果发现提交遍历序列但不向上回溯至起始目录不会影响应用程序的行为,那么在接下来的测试中,应该尝试遍历出起始目录,从服务器文件系统的其他地方访问文件。

渗透测试步骤

(1)如果所攻击的应用程序功能只拥有文件读取访问权限,那么尝试访问相关操作系统上的一个已知任何用户均可读取的文件。提交下面其中一个值作为受控制的文件名参数:

幸运的话,浏览器将显示请求的文件的内容,如图10-5所示。

图10-5 成功的路径遍历攻击

(2)如果所攻击的功能拥有文件写入访问权限,那么要最终确定应用程序是否易于受到攻击,可能会更困难。通常,一种有效的测试是尝试写入两个文件,一个文件可被任何用户写入,另一个文件即使是根用户或管理员也禁止写入。例如,在Windows平台上可以尝试写入下面两个文件:

在UNIX平台上,禁止根用户写入的文件取决于使用的平台版本,但尝试用一个文件重写一个目录绝不可能取得成功,因此可以进行以下尝试:

在上面的每对测试中,如果应用程序在响应两个请求时表现出行为差异(例如,响应第二个请求时返回一条错误消息,而响应第一个请求时不返回错误消息),那么应用程序可能易于受到攻击。

(3)还有另一种方法可通过写入访问确定遍历漏洞,即尝试在Web服务器的Web根目录中写入一个新文件,然后尝试通过浏览器获得这个文件。但是,如果并不知道Web根目录的位置,或者访问文件的用户并不拥有写入权限,这种方法可能不会成功。

 注解 几乎每一种文件系统都接受试图向上回溯到文件系统根目录的多余遍历序列。因此,当探查漏洞时,像在上面的示例中一样,最好提交大量的遍历序列。附加数据的起始目录可能位于文件系统的“深处”,因此使用大量的序列有助于避免错误警报。

而且,Windows平台接受斜线(/)和反斜线(\)作为目录分隔符,而UNIX平台只接受斜线作为分隔符。另外,一些Web应用程序过滤两者之一。即使完全确信Web服务器运行的是UNIX操作系统,但应用程序仍然可能被Windows后端组件调用。为此,当探查遍历漏洞时,最好两者都进行测试。

避开遍历攻击障碍

最初的遍历攻击尝试(如前所述)并未成功,并不意味着应用程序不容易受到攻击。许多应用程序开发者意识到路径遍历漏洞,并执行各种输入确认检查尝试防止这种漏洞。但是,这些防御措施往往存在缺陷,可被技术熟练的攻击者轻易避开。

第一种常见的输入过滤方法如下,首先检查文件名参数中是否存在任何路径遍历序列,如果存在,要么拒绝包含遍历序列的请求,要么尝试删除该序列,以对输入进行净化。这种类型的过滤往往易于受到各种攻击,它们使用编码或其他方法来避开过滤。这类攻击全都利用输入确认机制所面临的规范化问题,我们在第2章已经讲过。

渗透测试步骤

(1)尝试始终通过使用斜线与反斜线的路径遍历序列进行测试。许多输入过滤仅检查其一种序列,而文件系统却支持全部两种序列。

(2)尝试使用下面的编码方案,对遍历序列进行简单的URL编码。一定要对输入中的每个斜线与点进行编码:

(3)尝试使用下面的16位Unicode编码:

(4)尝试使用下面的双倍URL编码:

(5)尝试使用下面的超长UTF-8 Unicode编码:

可以在Burp Intruder中使用非法Unicode有效载荷类型为任何特殊字符生成大量其他形式的表示法,并将它提交到目标参数的相关位置。这些表示法严重违反了Unicode表示法规则,但却为许多Unicode解码器接受,特别是Windows平台上的解码器。

(6)如果应用程序尝试通过删除遍历序列来净化用户输入,但没有以递归的方式应用这种过滤,那么可以用一个序列替换另一个序列来避开过滤。例如:

尝试访问

http://mdsec.net/filestore/30/

http://mdsec.net/filestore/39/

http://mdsec.net/filestore/46/

http://mdsec.net/filestore/59/

http://mdsec.net/filestore/65/

第二种防御路径遍历攻击时常用的输入过滤,就是确认用户提交的输入是否包含应用程序想要的后缀(如文件类型)或前缀(如起始目录)。这种类型的防御可以与前面描述的过滤联合使用。

渗透测试步骤

(1)一些应用程序检查用户提交的文件是否以一种或一组特殊的文件类型结尾,并拒绝访问其他内容的请求。有时候,可以在请求的文件名后放入一个URL编码的空字节,在后面连接应用程序接受的文件类型,从而避开这种检查。例如:

这种攻击有时会成功,是因为应用程序使用API在托管执行环境下执行文件类型检查,该执行环境允许字符串包含空字符(如Java中的String.endsWith())。但是,当获取文件时,应用程序最终在一个无法控制的环境(其中的字符串以空白字符结束)中使用API,因此文件名被截短为想要的值。

(2)一些应用程序将它们自己的文件类型后缀附加在用户提交的文件名后,尝试控制被访问的文件类型。在这种情况下,基于相同的原因,前面的任何一种利用都可能取得成功。

(3)一些应用程序检查用户提交的文件名的开头部分是否为起始目录的某一个子目录,或者一个特殊的文件名。当然,通过以下方法可轻易避开这种检查:

(4)如果以上针对输入过滤的攻击都无法成功,可能应用程序实施了几种类型的过滤,因此需要同时使用上面的几种攻击方法(同时针对遍历序列过滤与文件类型或目录过滤)。遇到这种情况,如有可能,最好的办法是将问题分解成几个独立的阶段。例如,如果请求

能够成功,但请求

却导致失败,那么尝试使用所有可能的遍历序列,直到第二个请求获得成功。如果使用这些成功的遍历序列仍然无法访问/etc/passwd,就请求以下文件,检查应用程序是否实施任何文件类型过滤,以及是否可以避开这种过滤:

彻底检查应用程序定义的起始目录,设法了解它实施的全部过滤,看是否可以利用上述技巧避开每一种过滤。

(5)当然,如果能够随意访问应用程序,那么攻击就变得更加简单,因为渗透测试员可以系统性地攻击每种输入,并最终确定通过哪些文件名(如果有)可以到达文件系统。

处理定制编码

我们曾经见到,应用程序采用的文件名编码方案最终以危险的方式进行处理,模糊处理也不能提供任何安全保障,这时就会出现最为可怕的路径遍历漏洞。

例如,一些应用程序具有某种工作流程功能,允许用户上传与下载文件。执行上传操作的请求提供一个文件名参数,它在写入文件时易于受到路径遍历攻击。如果一个文件成功上传,那么应用程序再为用户提供一个下载URL。这里有两点值得注意。

应用程序核对将要写入的文件是否已经存在,如果存在,就拒绝重写这个文件。

为下载用户文件而生成的URL使用一种定制模糊处理方案表示。这种方案似乎是一种定制的Base64编码形式,它在每个编码文件名位置使用一组不同的字符。

总的来说,这些注意点给直接利用漏洞设立了障碍。首先,尽管能够在服务器文件系统中写入任何文件,但攻击者却无法重写任何现有的文件,而且,Web服务器进程拥有的较低权限意味着攻击者不可能在任何有利位置创建新文件。其次,如果不对定制编码进行逆向工程,攻击者也不可能请求任意一个现有的文件(如/etc/passwd)。

一个简单的实验就可以证明模糊处理后的URL包含用户最初提交的文件名字符串,如下所示。

编码后的URL之间的长度差异表明,在应用编码之前,应用程序并未对其进行路径规范化。这种行为让攻击者有机会对漏洞加以利用。首先通过以下名称提交一个文件:

它的规范化形式为:

因此可以被Web服务器写入。上传这个文件将生成以下包含模糊处理文件名的下载URL:

要修改这个值以返回文件/etc/passwd,只需从右边将其截短成

果然,尝试使用这个值下载文件时,结果返回服务器的passwd文件。这说明,服务器为攻击者提供了足够的资源,即使不解译模糊处理算法,攻击者也可以使用它的编码方案编码任意文件路径。

 注解 细心的读者可能已经注意到,在上传的文件名中出现了一个多余的./。这样做是必要的,因为它可确保截短后的URL符合Base64编码规则,在3字节的明文边界结束,并因此在4字节的编码文本边界结束。在服务器上解码时,从一个编码块的中间截取URL几乎肯定会造成错误。

利用遍历漏洞

确定一个可向服务器文件系统读取或写入任意文件的路径遍历漏洞后,渗透测试员应当实施哪种攻击利用这些漏洞呢?许多时候,在文件系统上拥有和Web服务器进程相同的读取/写入权限。

渗透测试步骤

可以利用读取访问路径遍历漏洞从包含有用信息的服务器上获取有益的文件,或者帮助优化针对其他漏洞的攻击。如下所示。

操作系统与应用程序的密码文件。

服务器与应用程序配置文件,以发现其他漏洞或优化另一次攻击。

可能含有数据库证书的包含文件。

应用程序使用的数据源,如 MySQL 数据库文件或XML文件。

服务器可执行页面的源代码,以执行代码审查,搜索漏洞(例如GetImage.aspx?file=GetImage.aspx)。

可能包含用户名和会话令牌的应用程序日志文件等。

如果发现一个允许写入访问的路径遍历漏洞,那么渗透测试的主要目标应该是利用它在服务器上执行任意命令。利用漏洞实现这一目标的方法包括以下几种。

在用户的启动文件夹中创建脚本。

当用户下一次连接时,修改in.ftpd等文件执行任意命令。

向一个拥有执行许可的Web目录写入脚本,从浏览器调用它们。

防止路径遍历漏洞

迄今为止,避免向任何文件系统API传送用户提交的数据是防止路径遍历漏洞的最有效方法。许多时候,包含在最初的GetIfile.aspx?filename=keira.jpg示例中,应用程序完全没有必要实施防御。因为大多数文件都没有采用访问控制,攻击者可将这些文件存入Web根目录中,再通过URL直接访问。如果以上方法行不通,应用程序可能会保存一个可由页面处理的硬编码图像文件列表,并使用不同的标识符(如一个索引号)指定需要的文件。任何包含无效标识符的请求都可能遭到拒绝,因为没有受攻击面可供用户利用,使其操纵页面提供的文件路径。

有时,与实现文件上传与下载的工作流程功能一样,应用程序可能需要允许用户通过名称指定文件,这时,开发者可能采用最简单的办法,将用户提交的用户名传送给文件系统API,从而达到这种目的。在这种情况下,应用程序应实施深层防御措施,为路径遍历攻击设立几层障碍。

以下是一些可能有用的防御方法,在应用过程中,最好将它们组合在一起使用。

对用户提交的文件名进行相关解码与规范化之后,应用程序应检查该文件名是否包含路径遍历序列(使用反斜线或斜线)或空字节。如果是这样,应用程序应停止处理请求。不得尝试对恶意文件名进行任何净化处理。

应用程序应使用一个硬编码的、允许访问的文件类型列表,并拒绝任何访问其他文件类型的请求(完成上述解码与规范化之后)。

对用户提交的文件名进行一切必要的过滤后,应用程序应使用适当的文件系统 API 确认是否一切正常,以及使用该文件名访问的文件是否位于应用程序指定的起始目录中。

在Java中,可通过使用用户提交的文件名示例一个java.io.File对象,然后对这个对象调用getCanonicalPath方法,即可达到上述目的。如果这个方法返回的字符串并不以起始目录的名称开头,那么用户已通过某种方式避开了应用程序的输入过滤,因而应用程序应拒绝执行该请求。

在ASP.NET中,可以将用户提交的文件名传送给System.Io.Path.GetFullPath方法,并对返回的字符串执行和上述Java中一样的检查,从而达到相同的目的。

应用程序可以使用一个chrooted环境访问包含被访问文件的目录,减轻大多数路径遍历漏洞造成的影响。在这种情况下,chrooted目录就好比是文件系统根目录,任何试图从这个目录向上回溯的多余遍历请求都被忽略。大多数UNIX平台都支持chrooted文件系统。在Windows平台上,以新逻辑驱动器的形式安装相关起始目录,并且使用相应的驱动器字母访问目录内容,即可实现类似的效果(至少对遍历漏洞而言是这样)。

应用程序应将其路径遍历攻击防御机制与日志和警报机制整合在一起。任何时候,只要收到一个包含路径遍历序列的请求,提出请求的用户就可能心存恶意,应用程序应在日志中进行记录,标明该请求企图违反安全机制,并终止该用户的会话。如有可能,应冻结该用户账户并向管理员发出警报。

文件包含漏洞

许多脚本语言支持使用包含文件(include file)。这种功能允许开发者把可重复使用的代码插入到单个的文件中,并在需要时将它们包含在特殊功能的代码文件中。然后,包含文件中的代码被解释,就好像它插入到包含指令的位置一样。

远程文件包含

PHP语言特别容易出现文件包含漏洞,因为它的包含函数接受远程文件路径。这种缺陷已经成为PHP应用程序中大量漏洞的根源。

以一个向不同位置的人们传送各种内容的应用程序为例。用户选择他们的位置后,这个信息通过一个请求参数传送给服务器,代码如下:

应用程序通过以下方式处理Country参数:

这使执行环境加载位于Web服务器文件系统中的US.php文件。然后,这个文件的内容被复制到main.php文件中,并得以执行。

攻击者能够以各种方式利用这种行为,最严重的情况是指定一个外部URL作为包含文件的位置。PHP包含函数接受这个位置作为输入,接着,执行环境将获取指定的文件并执行其内容。因此,攻击者能够构建一个包含任意复杂内容的恶意脚本,将其寄存在他控制的Web服务器上,并通过易受攻击的应用程序函数调用它然后执行。例如:

本地文件包含

有时,应用程序根据用户可控制的数据加载包含文件,但这时不可能给位于外部服务器上的文件指定URL。例如,如果用户可控制的数据被提交给ASP函数Server.Execute,那么攻击者就可以执行任意一段ASP脚本,只要这段脚本属于调用这个函数的相同应用程序。

在这种情况下,攻击者仍然可以利用应用程序的行为执行未授权操作。

在服务器上可能有一些通过正常途径无法访问的文件,例如,任何访问路径/admin的请求都会被应用程序实施的访问控制阻止。如果能够将敏感功能包含在一个授权访问的页面中,那么就可以访问那个功能。

服务器上的一些静态资源也受到同样的保护,无法直接访问。如果能够将这些文件动态包含在其他应用程序页面中,那么执行环境就会将静态资源的内容复制到它的响应中。

查找文件包含漏洞

任何用户提交的数据项都可能引起文件包含漏洞。它们经常出现在指定一种语言或一个位置的请求参数中,也常常发生在以参数形式传送服务器端文件名的情况下。

渗透测试步骤

要测试远程文件包含漏洞,执行以下步骤。

(1)向每一个目标参数提交一个连接受控制的Web服务器资源的URL,并确定是否收到运行目标应用程序的服务器提出的任何请求。

(2)如果第一次测试失败,尝试提交一个包含不存在的IP地址的URL,并确定服务器试图与这个地址建立连接时是否出现超时。

(3)如果发现应用程序易于受到远程文件包含攻击,与前面描述的动态执行攻击中一样,使用相关语言中可用的API,构建一段恶意脚本实施攻击。

相对于远程文件包含而言,存在本地文件包含漏洞的脚本环境要更多一些。要测试本地文件包含漏洞,执行以下步骤。

(1)提交服务器上一个已知可执行资源的名称,确定应用程序的行为是否有任何变化。

(2)提交服务器上一个已知静态资源的名称,确定它的内容是否被复制到应用程序的响应中。

(3)如果应用程序易于受到本地文件包含攻击,尝试通过Web服务器访问任何无法直接到达的敏感功能或资源。

(4)测试能否利用之前讲到的遍历技巧访问其他目录中的文件。

注入XML 解释器

今天的Web应用程序大量使用XML,在浏览器与前端应用程序服务器之间传送的请求和响应,以及在后端应用程序组件(如SOAP服务)之间传送的消息中都可以找到XML。如果使用专门设计的输入破坏应用程序的运行并执行某些未授权操作,这些位置就易于受到各种攻击。

注入XML外部实体

在今天的Web应用程序中,XML常用于从客户端向服务器提交数据。然后,服务器端应用程序将处理这些数据,并且可能会返回一个包含XML或任何其他格式数据的响应。在使用异步请求在后台进行通信的基于Ajax的应用程序中,这种行为最为常见。浏览器扩展组件和其他客户端技术也可能会用到XML。

以一个使用Ajax实现的、提供无缝用户体验的搜索功能为例。在用户输入搜索词时,客户端脚本将向服务器提出以下请求:

服务器的响应如下所示(无论响应采用什么格式,其中都可能存在漏洞):

客户端脚本将处理该响应,并用搜索结果对用户界面进行更新。

如果遇到这种类型的功能,应当始终检查其是否存在XML外部实体(XXE)注入漏洞。之所以会出现这种漏洞,是因为标准的XML解析库支持使用实体引用。这些引用仅仅是在XML文档内部或外部引用数据的一种方法。通过阅读本书的其他内容,你应该很熟悉实体引用。例如,与<和>字符对应的实体如下所示:

XML格式允许在XML文档中定义定制实体。这些实体在文档开始部分的可选DOCTYPE元素中定义。例如:

如果文档中包含以上定义,解析器将用testrefvalue这个已定义的值替代文档中出现的任何&testref;实体引用。

此外,XML规范允许使用外部引用来定义实体,XML解析器将动态提取这些实体的值。这些外部实体定义采用URL格式,并可以引用外部Web URL或本地文件系统上的资源。XML解析器将提取指定URL或文件的内容,并将其作为已定义实体的值。如果应用程序在其响应中返回任何使用外部定义的实体的XML数据,则指定文件或URL的内容将在该响应中返回。

攻击者可以通过向XML添加适当的DOCTYPE元素,或通过修改该元素(如果它已经存在),在基于XML的请求中指定外部实体。外部实体引用使用SYSTEM关键字来指定,并使用URL(可能使用file:协议)进行定义。

在前一个示例中,攻击者可以提交以下请求(该请求定义一个引用服务器文件系统上的文件的XML外部实体):

收到这个请求后,XML解析器将提取指定文件的内容,并使用该内容来替代已定义的实体引用(攻击者已在SearchTerm元素中使用了该实体引用)。由于SearchTerm元素的值会在应用程序的响应中回显,这会导致服务器以该文件的内容作出响应,如下所示:

尝试访问

http://mdsec.net/search/128/

除使用file:协议来指定本地文件系统上的资源外,攻击者还可以使用http:等协议让服务器通过网络提取资源。这些URL可以指定任意主机、IP地址和端口。攻击者可以利用它们与后端系统上无法通过因特网直接访问的网络服务进行交互。例如,以下攻击尝试连接到在专用IP地址192.168.1.1的端口25上运行的邮件服务器:

通过这种技巧可以实施各种攻击,如下所示。

攻击者可以将应用程序作为代理服务器使用,从应用程序能够访问的任何Web服务器上检索敏感内容,包括那些在组织内部的专用非路由地址空间运行的内容。

攻击者可以利用后端Web应用程序中的漏洞,只要这些漏洞可以通过URL加以利用。

攻击者可以通过攻击大量IP地址和端口号,对后端系统上的开放端口进行测试。在某些情况下,可以使用时间性差异来推断所请求的端口的状态。其他时候,应用程序可能会在响应中返回某些服务的服务标题(service banner)。

最后,如果应用程序检索外部实体,但并不在响应中返回该实体,则攻击者仍然可以通过无期限地读取某个文件流,从而实施拒绝服务攻击。

注入SOAP

SOAP(Simple Object Access Protocol,简单对象访问协议)是一种使用XML格式封装数据、基于消息的通信技术。各种在不同操作系统和架构上运行的系统也使用它来共享信息和传递消息。它主要用在Web服务中;通过浏览器访问的Web应用程序常常使用SOAP在后端应用程序组件之间进行通信。

由不同计算机执行单项任务以提高性能的大型企业应用程序经常使用SOAP。采用Web应用程序作为现有应用程序前端的情况也经常可以见到SOAP的身影。这时,应用程序通常使用SOAP在不同的组件之间通信,以确保模块性和互用性。

由于XML是一种解释型语言,因此,和前面描述的其他示例一样,SOAP也易于受到代码注入攻击。XML元素通过元字符<、>和/以语法形式表示。如果用户提交的数据中包含这些字符,并被直接插入SOAP消息中,攻击者就能够破坏消息的结构,进而破坏应用程序的逻辑或造成其他不利影响。

以一个银行应用程序为例,一名用户正使用下面的HTTP请求进行转账:

在处理这个请求的过程中,应用程序在两个后端组件之间传送下面的SOAP消息:

注意消息中的XML元素如何与HTTP请求中的参数对应起来,以及应用程序如何添加ClearedFunds元素。这时,应用程序逻辑确定账户中没有足够的资金可进行转账,并将这个元素(ClearedFunds)的值设为False,因此收到SOAP消息的组件将拒绝转账。

在这种情况下,攻击者可以通过各种方法注入SOAP消息,从而破坏应用程序的逻辑。例如,提交下面的请求会在最初的元素之前插入另外一个ClearedFunds元素(同时保持SQL语法的有效性)。如果应用程序处理它遇到的第一个ClearedFunds元素,那么即使账户中没有资金,也可以成功进行转账。

另一方面,如果应用程序处理它遇到的后一个ClearedFunds元素,攻击者就可以在ToAccount参数中注入一个类似的攻击。

另一种类型的攻击是使用XML注释完全删除原始SOAP消息中的一个元素,并用攻击者自己设计的元素代替被删除的元素。例如,下面的请求通过Amount参数注入一个ClearedFunds元素,为ToAccount元素建立一个起始标签,开始一段注释,并在ToAccount参数中结束注释,从而保持XML语法的有效性:

另一种攻击是尝试在一个注入的参数内完成整个SOAP消息,并将消息的剩余部分注释掉。但是,由于没有结束注释与起始注释相匹配,这种攻击会生成完全错误的XML语法,从而被许多XML解析器拒绝。这种攻击并不能在所有XML解析库中起作用,它只对定制或自主研发的XML解析器奏效。

尝试访问

这个示例中包含一个错误消息,有助于对攻击进行微调:

http://mdsec.net/bank/27/

下面的示例包含同样的漏洞,但反馈信息很少。由此可见,如果错误消息没有什么提示信息,利用SOAP注入有多难!

http://mdsec.net/bank/18/

http://mdsec.net/bank/6/

查找并利用SOAP注入

SOAP注入可能很难发现,因为随意提交XML元字符会破坏SOAP消息的格式,而且这样做生成的错误消息也极其简单。但是,使用下面的步骤依然可以相对可靠地检测出SOAP注入漏洞。

渗透测试步骤

(1)轮流在每个参数中提交一个恶意XML结束标签,如</foo>。如果没有发生错误,那么输入可能没有插入到SOAP消息中,或者以某种方式被净化了。

(2)如果出现错误,提交一对有效的起始与结束标签,如<foo></foo>。如果这对标签使错误消失,那么应用程序很可能易于受到攻击。

(3)有些时候,插入到XML格式消息中的数据随后以XML格式被读取并返回给用户。如果修改的数据项在应用程序的响应中返回,看看提交任意XML内容是否会以相同的形式返回,或者已通过某种方式被规范化。轮流提交下面两个值:

如果发现其中一个值的返回结果为另一个值,或者只返回test,那么可以确信输入被插入到了XML消息中。

(4)如果HTTP请求中包含几个可放入SOAP消息的参数,尝试在一个参数中插入起始注释字符<!--,在另一个参数中插入结束注释字符!-->。然后,轮换在参数中插入这两个字符(因为无法知道参数出现的顺序)。这样做可能会把服务器SOAP消息的某个部分作为注释处理,从而改变应用程序的逻辑,或者形成一个可能造成信息泄露的不同错误条件。

如果SOAP注入很难发现,就更难对其加以利用。许多时候,需要知道数据周围的XML的结构,以提交专门设计的输入,修改消息内容而不致破坏它的结构。在前面描述的所有测试中寻找任何揭示SOAP消息处理细节的错误消息。幸运的话,一条详细的错误消息将透露SOAP消息的完整内容,允许构建专门设计的值查找相关漏洞。如果不够幸运,就只能纯粹猜测,这样攻击成功的几率就非常低。

防止SOAP注入

我们可以在用户提交的数据被插入SOAP消息中的任何位置实施边界确认过滤,以防止SOAP注入。需要进行过滤的数据包括用户在当前请求中直接提交的数据,以及在前面的请求中已经存在或由以用户数据为输入的其他处理过程生成的数据。

为防止上述攻击,应用程序应对出现在用户输入中的任何XML元字符进行HTML编码。HTML编码包含用对应的HTML实体替代字面量字符。这样做可确保XML解释器在进行处理时,把它们当做相关元素的数据值,而不是消息结构的一部分。一些经常造成问题的字符的HTML编码如下:

注入后端HTTP 请求

在上一节中,我们介绍了一些应用程序如何将用户提交的数据合并到后端HTTP请求中,以请求用户无法直接访问的服务。更常见的情况是,应用程序可能会将用户输入嵌入任何类型的后端HTTP请求,包括那些以常规名/值对传输参数的请求。由于应用程序通常会有效代理用户提交的URL或参数,因而这种行为往往易于受到攻击。针对这种功能的攻击可以分为以下类别:

服务器端HTTP重定向:攻击者可以通过这种方法指定任意资源或URL,然后再由后端应用程序服务器请求这些资源或URL。

HTTP参数注入(HPI):攻击者可以通过这种方法在应用程序服务器提出的后端HTTP请求中注入任意参数。如果攻击者注入后端请求中已存在的参数,就可以利用HTTP参数污染(HPP)攻击覆盖服务器指定的原始参数值。

服务器端HTTP重定向

如果应用程序接受用户可控制的输入,并将其合并到使用后端HTTP请求检索的URL中,这种行为就会导致服务器端重定向漏洞。用户提交的输入中可能包含被检索的完整URL,或者应用程序可能会对该URL进行某种处理,如添加标准的后缀。

后端HTTP请求可能指定公共因特网上的某个域,或者指定用户无法直接访问的内部服务器。所请求的内容可能对应用程序的功能非常关键,如支付网关的接口;或者较为次要,如从第三方提取的内容。这种技巧常用于将几个单独的内部和外部应用程序组件结合到一个前端应用程序中,再由该应用程序代表这些组件实施访问控制和会话管理。如果攻击者能够控制后端HTTP请求中的IP地址或主机名,他就可以使应用程序服务器连接到任意资源,有时甚至能够检索后端响应的内容。

以下面的前端请求为例,其中的loc参数用于指定客户端希望查看的CSS文件的版本:

如果没有在loc参数中为URL指定确认机制,攻击者就可以指定任何主机名来替代online.wahh-blogs.net。应用程序将检索指定的资源,导致攻击者将应用程序用作潜在的敏感后端服务的代理服务器。在下面的示例中,攻击者使应用程序连接到后端SSH服务:

应用程序的响应包含所请求的SSH服务的旗标:

攻击者可以利用服务器端HTTP重定向漏洞,将易受攻击的应用程序作为开放的HTTP代理服务器,以实施各种其他攻击。

攻击者可以将该代理服务器用于攻击互联网上的第三方系统。恶意流量针对的是运行易受攻击的应用程序的服务器上的目标。

攻击者可以将该代理服务器用于连接到组织内部网络中的任意主机,从而访问无法通过因特网直接访问的目标。

攻击者可以将该代理服务器用于反向连接在应用程序服务器本身上运行的其他服务,从而突破防火墙的限制,并利用信任关系来避开身份验证。

最后,攻击者可以通过使应用程序在响应中包含受控的内容,利用代理功能实施跨站点脚本等攻击(请参阅第12章了解详细信息)。

渗透测试步骤

(1)确定任何可能包含主机名、IP地址或完整URL的请求参数。

(2)对于每个参数,修改参数值以指定其他与所请求的资源类似的资源,并观察该资源是否会出现在服务器的响应中。

(3)尝试指定一个针对你控制的因特网服务器的URL,并对该服务器进行监视,检查来自所测试的应用程序的传入连接。

(4)如果没有收到任何传入连接,则监视应用程序响应所花费的时间。如果存在延迟,则说明应用程序的后端请求可能由于出站连接上的网络限制导致超时。

(5)如果你成功利用相关功能连接到任意URL,则可以尝试实施以下攻击。

(a)确定是否可以指定端口号。例如,可以指定http://mdattacker.net:22。

(b)如果可以指定端口号,尝试使用Burp Intruder等工具对内部网络进行端口扫描,以逐个连接到一系列IP地址和端口(请参阅第14章了解详细信息)。

(c)尝试连接到应用程序服务器的回环地址上的其他服务。

(d)尝试将受控的Web页面加载到应用程序的响应中,以实施跨站点脚本攻击。

 注解 一些服务器端重定向API,如ASP.NET中的Server.Transfer()和Server.Execute(),仅可重定向到同一主机上的相关URL。尽管如此,攻击者仍然可以向这些方法传递用户提交的输入,以利用信任关系,并访问受平台级身份验证保护的服务器上的资源。

尝试访问

http://mdsec.net/updates/97/

http://mdsec.net/updates/99/

HTTP参数注入

如果用户提交的参数被用作后端HTTP请求中的参数,这时就会导致HTTP参数注入(HPI)。以下面的之前易于受SOAP注入的银行转账功能(稍作修改)为例:

这个前端请求由用户的浏览器提出,将导致应用程序向银行基础架构中的另一台Web服务器提出其他HTTP请求。在以下后端请求中,应用程序从前端请求中复制了一些参数值:

这个请求要求后端服务器检查是否有清算资金可以转账,如果有,则进行转账。但是,前端服务器可以通过提供以下参数,指定存在清算资金,从而避开上述检查:

如果攻击者发现这种行为,他就可以尝试实施HPI攻击,在后端请求中注入clearedfunds参数。要注入该参数,他将所需参数附加到现有参数值的后面,并将分隔名称和值的&和=字符进行URL编码,如下所示:

当应用程序服务器处理这个请求时,它会以正常方式对参数值进行URL解码。因此,前端应用程序收到的ToAccount参数的值为:

如果前端应用程序没有确认这个值并将它按原样传递给后端请求,应用程序将提出以下后端请求,使攻击者能够成功避开清算资金检查:

尝试访问

http://mdsec.net/bank/48/

 注解 与SOAP注入不同,在后端请求中注入任意异常参数不会导致任何错误。因此,要想成功实施攻击,需要清楚了解应用程序具体使用了哪些后端参数。在黑盒环境下,很难确定这些信息;但是,如果应用程序使用任何可以获取或搜索其代码的第三方组件,那么就可以轻易获得这些信息。

HTTP参数污染

HPP是一种可用于各种环境下的攻击技巧(请参阅第12章和第13章了解其他示例),这种技巧常用在HPI攻击中。

如果请求中包含多个同名请求,这时Web服务器该如何处理?对于这一问题,HTTP规范并未提供任何指导。实际上,各种Web服务器的处理方式各不相同,以下是一些常见的处理方式。

使用参数的第一个实例。

使用参数的最后一个实例。

串联参数值,可能在参数之间添加分隔符。

构建一个包含所有请求值的数组。

在前面的HPI示例中,攻击者可以在后端请求中添加一个新参数。实际上,攻击者可以对其实施注入攻击的请求很可能已经包含一个与攻击者所针对的参数同名的参数。在这种情况下,攻击者可以使用HPI条件注入另一个同名参数。随后,应用程序将表现出何种行为,将取决于后端HTTP服务器如何处理重复的参数。这样,攻击者或许可以用他注入的参数值“覆盖”原始参数值。

例如,如果原始的后端请求为:

并且后端服务器使用任何重复的参数的第一个实例,则攻击者可以对前端请求中的FromAccount参数实施攻击,如下所示:

相反,在这个示例中,如果后端服务器使用任何重复的参数的最后一个实例,则攻击者可以对前端请求中的ToAccount参数实施攻击。

尝试访问

http://mdsec.net/bank/52/

http://mdsec.net/bank/57/

HPP攻击能否成功,在很大程度上取决于目标应用程序服务器如何处理多个同名参数,以及后端请求中的插入点是否准确。如果两种技术需要处理相同的HTTP请求,HPP攻击就会造成严重的后果。Web应用程序防火墙或反向代理可能会处理某个请求,并将其传递给Web应用程序,由Web应用程序抛弃变量,甚至是基于之前不相关的请求部分构建字符串。

欲了解常见应用程序服务器在处理同名参数时的不同行为,请参阅以下论文:

www.owasp.org/images/b/ba/AppsecEU09_CarettoniDiPaola_v0.8.pdf

攻击URL转换

许多服务器会在所请求的URL抵达时重写这些URL,再将它们映射到应用程序中的相关后端功能。除传统的URL重写外,服务器在处理REST风格的参数、定制导航包装器以及其他URL转换方法时都会进行URL重写。这种处理方式可能易受HPI和HPP攻击。

为了简化和便于导航,一些应用程序在URL的文件路径,而非查询字符串中插入参数值。通常,应用程序会通过一些简单的规则转换URL,然后将其转发给真正的目标。Apache中的以下mod_rewrite规则用于处理可公共访问的用户资料:

此规则接受非常简洁的请求,例如:

并将这些请求转换为后端请求,以便于用户管理页面user_mgr.php包含的view功能进行处理。它将marcus参数移入查询字符串并添加mode=view参数:

在这种情况下,攻击者就可以利用HPI攻击在经过重写的URL中注入另一个mode参数。例如,如果攻击者请求:

将URL编码的值嵌入经过重写的URL中,将得到:

讲HPP攻击的我们说到,这种攻击能否成功取决于服务器如何处理重复的参数。在PHP平台中,mode参数被视为具有值edit,因而攻击取得成功。

渗透测试步骤

(1)轮流针对每个请求参数进行测试,尝试使用各种语法添加一个新注入的参数。

%26foo%3dbar —— URL编码的&foo=bar

%3bfoo%3dbar —— URL编码的;foo=bar

%2526foo%253dbar —— 双重URL编码的&foo=bar

(2)确定任何修改后不会改变应用程序的行为的参数实例(仅适用于在修改后会在应用程序的响应中造成某种差异的参数)。

(3)在上一步确定的每个实例都可以实施参数注入。尝试在请求的不同位置注入一个已知的参数,看这样做是否可以覆盖或修改现有的某个参数。例如:

(4)如果这样做会将现有值替换为新值,确定是否可以通过注入一个由后端服务器读取的值来避开任何前端确认机制。

(5)用其他参数名称替换注入的已知参数,如第4章介绍应用程序解析和内容查找时所述。

(6)测试应用程序是否允许在请求中多次提交同一个参数。在其他参数前后,以及请求的不同位置(查询字符串、cookie和消息主体中)提交多余的值。

注入电子邮件

许多应用程序拥有一项允许用户通过应用程序提交消息的功能。例如,向支持人员报告问题或提供关于Web站点的反馈。这项功能一般通过邮件(或SMTP)服务器执行。通常,用户提交的输入被插入到邮件服务器处理的SMTP会话中。如果攻击者能够提交未被过滤或净化的专门设计的输入,就可以在这个会话中注入任意SMTP命令。

多数时候,应用程序允许用户指定消息的内容和自己的电子邮件地址(插入到生成电子邮件的From字段中),还可以指定消息的主题和其他细节。能够控制的任何字段都易于受到SMTP注入。

SMTP注入漏洞经常被垃圾邮件发送者利用,他们扫描因特网查找易受攻击的邮件表单,并使用它们生成大量垃圾电子邮件。

操纵电子邮件标头

以图10-6所示的表单为例,它允许用户发送关于应用程序的反馈。

图10-6 一个典型的站点反馈表单

在该表单中,用户可指定发件人(From)地址和邮件的内容。应用程序将这个输入传送给PHP mail()命令,由它建立邮件并与它配置的邮件服务器进行必要的SMTP会话。生成的邮件如下:

PHP mail()命令使用additional_headers参数为消息设定发件人地址。这个参数还可用于指定其他标头,包括Cc和Bcc,并用换行符分隔每个被请求的标头。因此,攻击者可以通过在From字段中注入这其中某个标头,将邮件发送给任意收件人,如图10-7所示。

图10-7 电子邮件标头注入攻击

这会导致mail()命令生成以下邮件:

SMTP命令注入

在其他情况下,应用程序可能会执行SMTP会话,或者将用户提交的输入传送给一个不同的组件以完成这一任务。这时,我们就可以直接在这个会话中注入任意SMTP命令,完全控制由应用程序生成的消息。

例如,以一个使用以下请求提交站点反馈的应用程序为例:

应用程序会使用以下命令开始一个SMTP会话:

 注解 SMTP客户端发出DATA命令后,应用程序送出电子邮件消息的内容,包括消息头和主体,然后发送一个点字符(.)。这告诉服务器消息已发送完毕,客户端可以发出其他SMTP命令,发送其他消息。

这时,攻击者可以在任何受控的电子邮件字段中注入任意SMTP命令。例如,他可以尝试注入Subject字段,如下所示:

如果应用程序易受攻击,那么会建立以下SMTP会话,它生成两个不同的电子邮件消息,其中第二个完全由攻击者控制:

查找SMTP注入漏洞

为了有效探查应用程序的邮件功能,需要测试每一个提交给与电子邮件有关的功能的参数,甚至那些最初可能与生成的消息无关的参数。

渗透测试员还应该测试每一种攻击,并在每个测试中使用Windows和UNIX形式的换行符。

渗透测试步骤

(1)应当轮流提交下面的每个测试字符串作为每一个参数,在相关位置插入电子邮件地址。

(2)留意应用程序返回的任何错误消息。如果这些错误与电子邮件功能中的任何问题有关,确定是否需要对输入进行调整,以利用漏洞。

(3)应用程序的响应可能并不会以任何形式表示一个漏洞存在或被成功利用。应该监控指定的电子邮件地址,看是否收到任何电子邮件。

(4)仔细检查生成相关请求的HTML表单。它们可能提供与服务器端使用的软件有关的线索。其中可能包含一个用于指定电子邮件收件人地址的隐藏或禁用字段,可以直接对其进行修改。

 提示 向应用程序支持人员发送电子邮件的功能常常被视为外围功能,应用程序可能并不对其采用与主要功能相同的安全标准,或者进行严格的测试。而且,因为它们需要连接不常用的后端组件,应用程序往往通过直接调用相关操作系统命令来执行它们。因此,除探查SMTP注入漏洞外,还应极其仔细地检查所有与电子邮件有关的功能,查找OS命令注入漏洞。

防止SMTP注入

如果对提交给电子邮件功能或SMTP会话使用的任何用户提交的数据进行严格的确认检查,就可以防止SMTP注入漏洞。因此,应根据其用途对每项数据进行尽可能严格的确认。

应根据一个适当的正则表达式检查电子邮件地址(当然应拒绝所有换行符)。

消息主题不得包含任何换行符,并应实施适当的长度限制。

如果消息内容被一个SMTP会话直接使用,那么应禁止使用仅包含一个点字符的消息行。

小结

我们已经分析了一系列针对后端应用程序组件的攻击,了解到确定并利用每一种漏洞所需采取的实际步骤。许多现实世界的漏洞,使用应用程序后立即就会发现,例如,通过在搜索框中输入异常语法进行搜索。另外,这些漏洞可能隐藏得非常深,极少给应用程序造成可以检测的行为差异,也无法通过提交并操纵专门设计的输入的多阶段过程发现。

要确定应用程序中存在的后端注入缺陷,需要进行耐心仔细的检测。实际上,几乎每一种注入缺陷都会在处理用户提交的数据过程中表露出来,这些数据包括查询字符串参数的名称与值、POST数据、cookie以及其他HTTP消息头。许多时候,只有在全面探查了相关参数,明确了解应用程序对输入执行了何种类型的处理,并排除测试过程中的障碍后,漏洞才会显露出来。

面对后端应用程序组件的攻击造成了大量潜在的受攻击面,渗透测试员可能觉得对应用程序实施任何严重的攻击都必须付出巨大的努力。然而,从很大程度上讲,实施有效攻击需要从直觉上了解漏洞的位置,以及如何对其加以利用。获得这种直觉的唯一途径是进行实践,针对在现实中遇到的应用程序演练前面描述的技巧,并观察它们如何应对这些攻击。

问题

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

(1)某网络设备提供用于执行设备配置的Web界面。为什么这种功能通常易于受到操作系统命令注入攻击?

(2)在测试以下URL时:

将country参数的值更改为foo导致以下错误消息:

可以采取哪些步骤对应用程序实施攻击?

(3)在对一个在POST请求中以XML格式传送数据的应用程序进行测试时,可以利用哪种漏洞从服务器的文件系统中读取任意文件?要成功实施攻击,必须满足哪些先决条件?

(4)向ASP.NET平台上运行的应用程序提出以下请求:

应用程序执行以下代码:

请问param变量的值是什么?

(5)HPP是HPI的前提,还是HPI是HPP的前提?

(6)某应用程序包含一项功能,该功能向外部域提出请求,并返回这些请求的响应。为防止服务器端重定向攻击检索应用程序自己的Web服务器上的受保护资源,应用程序阻止了以localhost或127.0.0.1为目标的请求。如何突破这种防御,以访问服务器上的资源?

(7)某应用程序使用一项用于提交用户反馈的功能。该功能允许用户提交他们的电子邮件地址、邮件主题及详细的反馈。然后,应用程序以用户提交的主题和反馈为邮件正文,从用户的电子邮件地址向feedback@wahh-app.com发送一封电子邮件。以下哪一种方法能够有效防御邮件注入攻击?

(a)在邮件服务器上禁用邮件中继。

(b)使用feedback@wahh-app.com硬编码RCPT TO字段。

(c)确保用户提交的输入不包含任何换行符或其他SMTP元字符。

浙ICP备11005866号-12