3.1 服务器端HPP
在服务器端HPP中,尝试给服务器端发送非预期信息来让服务器端代码返回非预期的结果。在第1章中讨论过,当你发送一个请求到一个网站时,网站服务器端会处理该请求并且返回结果。在某些情况下,服务器端不仅返回一个网页,同时还会根据从URL获取到的信息运行一些代码。这部分代码只会在服务器端运行,所以对你来说基本上是不可见的,也就是说,你可以看到发送的信息和返回的结果,但是这中间运行的代码你是看不到的。因此,你只能推断这期间发生的事情。因为你看不到服务器端的代码结构,所以服务器端HPP取决于你如何确定存在潜在漏洞点的参数并进行试验。
让我们看一个例子:如果银行通过网站接收URL参数发起转账,可能会造成服务器端HPP漏洞。想象一下如果你可以通过输入三个URL参数from、to、amount来进行转账,每个参数按顺序指定:from指定要从中进行转账的账号,to指定要转入的账号,amount指定要转账的金额。以下URL表示从12345账户转账5000美元到67890账户:
银行可能会假设它只可能接收一个from参数,但是如果你提交两个参数会发生什么?就像下面这个URL一样:
这个URL最初的结构组成和第一个示例一样,只是多加了一个额外的from参数以表示另一个发送账户ABCDEF。在这种情况下,攻击者可以发送这个额外的参数,希望可以令程序用第一个from参数做转账验证,使用第二个from参数从账户ABCDEF提取资金。所以,如果银行信任它接收到的最后一个from参数,攻击者就可以利用一个不是他们自己的账户来执行转账操作——不再从12345的账户向67890的账户转账5000美元,取而代之的是,服务器端代码会使用第二个参数从ABCDEF账户转账给67890账户。
当服务器接收到多个具有相同名称的参数时,它可以通过多种方式进行响应。例如,PHP和Apache取参数的最后一次赋值,Apache Tomcat取参数的第一次赋值,ASP和IIS取该参数的所有赋值,等等。研究人员Luca Carettoni和Stefano di Paolo在App Sec EU 09大会上详细介绍了服务器端技术之间的许多差异,该内容现在可在OWASP网站上找到,网址为https://www.owasp.org/images/b/ba/AppsecEU09_CarettoniDiPaola_v0.8.pdf(请参阅该网站中的幻灯片9)。因此,没有哪一个单独的验证程序可以保证能同时处理好多个相同名称的参数提交操作,发现HPP漏洞时,你需要做一些试验来确认你所测试的网站是如何运作的。
银行的例子使用了显而易见的参数。但是有时HPP漏洞是由服务器端产生的,这些代码是无法直接观察到的。例如,假设你的银行决定修改其处理转账的方式,并更改其后端代码,从而在URL中不包含from参数。这次,银行将使用两个参数,一个参数代表转账的目的账户,另一个参数代表转账金额。转账源账户将由服务器端设置,你看不到该账户。一个示例链接可能如下所示:
通常,我们很难看懂服务器端代码,但是对于本例而言,我们知道银行服务器端的Ruby代码如下所示(明显存在冗余):
该代码创建了两个函数:prepare_transfer和transfer_money。prepare_transfer函数采用一个名为params❶的数组,该数组包含URL中的to和amount参数。该数组赋值为[67890,5000],其中数组值夹在方括号之间,并且每个值都用逗号分隔。函数的第一行❷将代码前面定义的用户账户信息添加到数组的末尾。我们最后在params中得到[67890,5000,12345]数组,然后将params传递给transfer_money函数❸。请注意,与参数不同,数组不是键值对组合,因此代码始终按顺序包含数组的每一个值:第一个值是转账目的账户,第二个值是转账金额,最后一个值是转账源账户。在transfer_money函数中,由于函数将每个数组值赋值给变量,因此值的顺序变得很明显。由于数组位置从0开始编号,因此params[0]访问数组中第一个位置的值(在这种情况下为67890),并将其分配给❹处变量。其他值也依次分配给❺和❻处变量。然后,将变量名传递到此代码段中未显示的transfer函数,该函数接受值并进行转账。
理想情况下,URL参数将始终按照代码期望的方式进行格式化。但是,攻击者可以通过将from的值传递给params来更改此逻辑的结果,例如以下URL:
在这种情况下,from参数也包含在传递给prepare_transfer函数的params数组中。因此,数组的值将为[67890,5000,ABCDEF],在❷处代码将用户账户赋值之后,数组变为[67890,5000,ABCDEF,12345]。结果是,在prepare_transfer中调用transfer_money函数时,from变量将从params数组的第三个参数赋值,虽然期望获取的user.account值为12345,但实际上将获取到攻击者传递的值ABCDEF❹。