4.1.1 用户名/密码威胁

尽管实施基本的用户名/密码验证有许多种方式,但Web实现通常都成为相同攻击类型的猎物:

·用户名枚举

·密码猜测

·窃听

在这个小节,我们将讨论这些攻击类型以及最容易遭到它们攻击的常见Web验证协议。

注意  我们不提供本章列出的任何攻击的危险等级,因为这些都是一般的攻击类型,危险等级取决于攻击的特定实现。

用户名枚举

用户名枚举主要用于提高密码猜测的效率。这种方法避免将时间浪费在不存在的用户上。例如,如果你能够确定没有名为Alice的用户,就没有必要浪费时间去猜测Alice的密码。下面是一些可能让你确定用户名的Web应用常用功能的示例:

剖析结果: 在第2章中,我们讨论了一些在网站中发现用户信息的场所,比如源代码注释。能干的攻击者总是检查剖析数据,因为这通常包含丰富的此类信息(在剖析信息中进行文本查找,寻找类似userid、username、user、usr、name、id和uid的字符串,通常能有所发现)。

在第8章中,我们还将讨论泄露用户名的常见网站结构——最明显的是服务供应商常常用于存放客户Web内容的按照用户命名的目录。

登录中的错误信息: 确定用户名是否存在的一种简单技术是尝试使用无效的凭据向Web应用进行验证,然后检查生成的错误信息。例如,尝试使用用户名Alice和密码abc123向Web应用验证。你可能碰到下面所列出的三种错误信息之一,除非你确实猜对了密码:

·你输入了一个错误的用户名。

·你输入了一个错误的密码。

·你输入了错误的用户名/密码组合。

如果你接收到第一个错误信息,该应用不存在此用户,你不应该再浪费时间去猜测Alice的密码。但是,如果你接收到第二个错误信息,你已经发现系统上一个有效的用户,可以继续猜测密码。最后,如果你接收到第三个错误信息,就很难确定Alice是不是有效的用户名(这提示应用设计人员在自己的验证机制中使用第三种信息)。

一个好的例子是Computer Associates(CA)公司的Web验证产品SiteMinder所实现的登录功能,CA公司通过2004年11月对Netegrity的收购取得这项技术。使用SiteMinder,你可以通过评估错误页面来进行用户名枚举。如果输入了错误的用户名,网站会尝试装入nouser.html。如果输入有效的用户名和不正确的密码,网站会尝试装入failedlogin.html。

自助密码恢复功能中的错误信息: 与刚刚讨论过的用户枚举漏洞相似,自助密码恢复(SSPR)功能也是用户枚举泄露漏洞的常见来源。SSPR是许多网站实现的一种功能,允许忘记密码或者无法验证的用户通过“自助服务”自己解决问题,最典型的实现是“忘记密码?”或者相似的链接,将新密码发给用户指定的电子邮件地址。该邮件地址通过备用机制“验证”用户,假定仅有出问题的用户能够访问该电子邮件账户读取新密码。

遗憾的是,以不安全的方式实现这一功能的应用常常会报告所提供的用户账户或者电子邮件地址是否有效。攻击者可以使用有效和无效的情况下不同的响应来检测账户是否存在。

除了用户枚举之外,在对SSPR请求的响应中随机地生成新密码也容易遭到拒绝服务(DoS)攻击。例如,特别恶意的攻击者可能创建一个脚本,重复地为每个发现的用户名请求新密码。如果请求重复的频率足够高,将会使目标账户被包含新密码的电子邮件淹没,使用户无法有足够的时间来使用新密码进行应用验证。

注册: 许多Web应用允许用户在注册过程中选择自己的用户名。这为确定用户名提供了另一个方向。在注册过程中,如果你选择另一个已经存在的用户,就可能得到一个错误如“请选择其他用户名”。只要你选择的用户名符合应用的原则并且不包含任何无效字符,这个错误信息就可能象征着所选择的用户名已经注册。在作出选择时,人们往往根据真实姓名创建用户名。例如,Joel Scambray可能选择Joel、JoelS、JScambray等用户名。因此,攻击者可以很快地根据电话簿、人口普查数据和其他网上资源中找到的真实姓名生成一个常用用户名列表。可以部署CAPTCHA技术来帮助缓解这些攻击的风险。CAPTCHA的详细信息参见本章4.2.3节中“用户注册攻击”。

账户锁定: 为了缓解密码猜测攻击的风险,许多应用在失败登录尝试一定次数之后会锁定账户。根据应用本身的风险,账户锁定阈值可能设置为3、5或者超过10次失败验证。许多高容量商业网站将锁定阈值设置得非常高(例如,100次失败的尝试)以应对与解锁用户账户相关的支持成本(越低的锁定阈值支持成本越高);同样,在易用/支持和安全之间的平衡取决于应用所面对的特定风险。应用也常常在30分钟、1小时或者24小时之后自动解锁账户。这也是为了减少重置账户所需要的支持电话。这种对策有效地降低了密码猜测攻击的速度,好的密码策略就是安全性和可用性的良好平衡。

但是,账户锁定只对有效用户名有意义。怎么锁定不存在的账户?这正是许多应用实现不正确的地方。例如,如果账户锁定被设置为3,如果账户不存在,它会被锁定吗?如果不会,你可能就偶然地发现了一种确定无效账户的方法。如果你锁定了一个账户,下一次登录时,应该接收到一个错误信息。但是,大部分应用不跟踪无效的账户。最后,避免用户名枚举遭遇账户锁定的最佳方法是完全不告诉用户他已被锁定。但是这么做几乎肯定会使用户感到沮丧和恼怒。

有时候,账户锁定使用客户端功能如JavaScript或者隐藏标记实现。例如,可能有一个变量或者域代表登录尝试次数。通过修改客户端JavaScript或者使用代理直接发出登录操作可以很简单地绕过客户端账户锁定(Burp Suite repeater功能很适合做这个工作;在第2章中已经讨论过Burp Suite)和JavaScript。

定时攻击: 如果其他方法都失败,定时攻击可能是受挫的攻击者的最后手段。如果你不能从错误信息、注册或者密码修改中枚举用户名,可以试试计算错误密码和用户名错误信息出现花费的时间。根据所实现的验证算法和使用的技术,各种响应(“错误用户名”和“错误密码”)花费的时间可能有显著的不同。观察响应时间的差异可以提供合法用户名和密码的线索。但是,为了使这种技术有效,这种差异必须足够大,以掩盖由于网络延时和负载引起的波动。记住,这种技术很可能制造大量的假警报。

在进入关于已知用户名的密码猜测的下一小节之前,我们应该提醒大家,尽管对允许攻击者确定用户名感到担心的安全专家已经提示了风险,但许多在线企业仍然接受这一做法。

密码猜测

密码猜测是用户名/密码验证方案的祸根,这一点都不令人吃惊。遗憾的是,这种方案在Web上很常见,从而成为了最基本的攻击技术的猎物。

密码猜测攻击的执行通常不考虑实际的验证协议。手工猜测总是可能的,当然,有一些自动化的客户端软件,能对最常用的协议执行密码猜测。我们接下来将讨论一些常见的密码猜测工具和技术。

手工密码猜测: 密码猜测攻击既可以通过手工也可以通过自动方法进行。手工密码猜测很乏味,但是我们发现人类的直觉常常是最好的自动化工具,特别是使用自定义错误页面来响应失败的基于表单登录尝试时。在进行密码猜测时,我们最喜欢的选择如表4-1所示。

表4-1 猜测攻击中使用的常见用户名和密码(大小写不敏感)

虽然表4-1中的列表有限,但是很好地说明了应用中常用的弱密码类型。使用自动化工具,可以使用整个用户名/密码猜测字典,比手工输入要快得多。一次简单的搜索引擎查询就能找到网上广泛可用的许多字典,包括聚焦于某种应用、硬件或者设备的裁剪过的字典。

自动化密码猜测: 自动化密码猜测有两种基本方法:深度优先和广度优先。深度优先算法尝试一个用户名的所有密码组合,然后尝试下一个用户名。这种方法可能很快触发账户锁定,因为在短时间里对相同的账户进行数百次的验证尝试。广度优先用相同密码尝试不同用户名的组合。因为不会连续对相同账户进行验证尝试,广度优先方法触发应用账户锁定机制的可能性较小。我们来看看一些当今的自动化Web密码猜测工具。

注意  自动化密码猜测可以对应用进行拒绝服务攻击。服务器上始终有不断增加的负载和锁定账户的风险。如果你是个攻击者,这种行为可能是有意的。但是如果你是测试者,应该确定是否有账户锁定并且相应地继续处理。

提示  如果密码策略合适并且强制执行,你可以将可能密码的集合减少为密码策略所允许的。例如,如果你知道密码策略只允许字母数字字符并且要求大小写字母的组合,就没有必要浪费时间在不包含数字的字典单词上。另一方面,如果你关注一个使用4位数字的ATM PIN作为密码的银行应用,就知道很有可能在大约5000次猜测中得到这一PIN/密码。

当今互联网上最常用的验证协议之一是HTTP基本验证。它最早在HTTP规范中定义,虽然它算不上巧妙,但也能胜任。基本验证有一些安全问题,这些问题都很好地在文档中记录(主要的问题是它以可简单解码的方式发送用户名/密码,并且在每个请求中都过于热心地发送这些凭据)。

当我们在咨询工作中遇到基本验证所保护的页面时,一般用Hydra来测试账户凭据强度。Hydra是一个简单的工具,采用用户名和密码(或者两者组合)的文本列表作为字典实施基本验证密码猜测。它以“HTTP 302 Object Moved”响应作为成功猜测的指示,寻找给定的用户名/密码文件中所有成功的猜测(也就是说,一旦找到第一个有效的账户就停止猜测)。下列示例显示Hydra在Windows上(通过Cygwin程序库)成功地猜测了一个HTTP基本验证密码。我们使用Hydra的-C选项来指定输入用户名/密码文件,攻击的是/secure目录(这在http-get参数之后指定):


D:\Toolbox>hydra -C list.txt victim.com http-get /secure
Hydra v5.0 (c) 2005 by van Hauser / THC - use allowed only for legal purposes.
Hydra (http://www.thc.org) starting at 2005-11-08 21:21:56
[DATA] 6 tasks, 1 servers, 6 login tries, ~1 tries per task
[DATA] attacking service http-get on port 80
[STATUS] attack finished for victim.com (waiting for childs to finish)
[80][www] host: 192.168.224.40 login: user password: guessme
Hydra (http://www.thc.org) finished at 2005-11-08 21:22:01

Hydra支持用http-head、http-get、https-head、https-get和http-proxy攻击Web应用。

WebCracker是一个较老的基于Windows的GUI应用,类似于Hydra,但是在我们的经验中不如Hydra那么容易定制。对于初学者或者进行账户密码强度的快速评估来说它是个很好的工具。图4-1显示WebCracker成功地猜测目标URL上的一些账户。

图4-1 WebCracker成功地猜测基本验证凭据

Brutus是一个通用密码猜测工具,它具备一些内建攻击例程,针对HTTP基本验证和基于表单的验证以及其他协议如SMTP和POP3。Brutus可以进行字典攻击(基于事先确定的单词列表如字典)以及暴力攻击(密码从给定的字符集如小写字母数字字符中随机生成)。图4-2显示进行一次基本验证密码猜测攻击之后的Brutus界面。

图4-2 Brutus密码猜测工具在19秒钟内猜测了4908个HTTP基本验证密码

Brutus也进行基于表单验证的攻击(我们将在4.1.2节中讨论)。令我们烦恼的一点是Brutus进行基于表单的攻击时不显示猜测过的密码。我们偶尔也发现它给出假结果,声称已经猜中了一个账户密码而实际上没有。但是,总体上对于密码猜测来说,Brutus的灵活性是罕有敌手的。

NTLM验证代理服务器: 集成的Windows验证(以前称作NTLM验证和Windows NT质询/响应验证)在HTTP上使用专利的Microsoft NT LAN Manager(NTLM)验证算法。这种算法主要由Microsoft Internet Explorer浏览器和IIS Web服务器实现,但是也可用于其他流行的软件,如Mozilla的Firefox浏览器通过对简单和受保护的GSS-API协商机制(Simple and Protected GSS-API Negotiation Mechanism,SPNEGO)互联网标准(RFC 2478)的支持,与Kerberos、NTLM或者操作系统支持的其他验证协议(如Microsoft Windows上的SSPI,Linux、Mac OS X和其他实现SPNEGO的Unix类操作系统上的GSS-API)协商。安全评估工具中对NTLM验证的支持这些年来已经有了很大的改进,在Paros和Burp客户端代理中也均有这种支持。如果你选择的工具不支持NTLM,可以通过Dmitry Rozmanov开发的NTLM验证代理服务器(APS)来获得这一支持。

提示  实现APS的详细描述可在本书网站上找到:http://www.webhackingexposed.com/ntlm-aps.html.

密码猜测对策

对密码猜测最有效的对策是组合运用强密码策略和强账户锁定策略。在少数几次不成功的登录尝试之后,应用就应该锁定账户以限制这种攻击暴露出来的内容。但是必须意识到,实施激进的账户锁定策略的应用可能将自己暴露在拒绝服务攻击之下。瞄准这种应用的恶意攻击者可能尝试通过重复的失败验证尝试来锁定系统上的所有账户。许多应用开发人员选择的一种较好的折中方案是暂时锁定账户一小段时间,比如10分钟。这减慢了密码猜测的速度,从而妨碍密码猜测攻击的效率。使用强密码策略,大大降低了攻击者随机地猜中一个密码的可能性。超过8个字母数字字符的大密钥空间密码,加上强账户锁定策略,就能减缓密码暴力攻击所造成的暴露。

最近,许多知名的网站如eBay开始跟踪IP地址并与你的账户关联。例如,尝试从不常见的IP或者在特定时间窗口内从不同的IP访问你的账户可能触发更多的验证或者需求(如CAPTCHA)。这些技术设计用来避免分布式或者自动化猜测攻击。某些财务网站已经实现更加强大的需求,例如发送一个文本消息,带有一个与账户相关的确认码。之后,必须把这个确认码提供给Web应用才能成功地验证。

注意  许多Web验证方案没有集成账户锁定功能——你必须实现自己的逻辑。

还有,我们已经提到过,在基于表单的验证中自定义错误页面是挫败脚本小孩的一种可行方法。这能阻止攻击者使用通用工具猜测密码。

这种方法的一个变种是使用全自动区分计算机和人类的图灵测试(Completely Automated Public Turing Tests to Tell Computers and Humans Apart,CAPTCHA)来欺骗自动化密码猜测例程(我们将在本章稍后更详细地讨论CAPTCHA)。

最后,遭到攻击时的情况总是值得了解的。下面是来自遭到基本验证密码猜测工具攻击的服务器的一个简短W3C格式的日志片段示例。可以看到,用于执行暴力攻击的工具——Brutus作为user-agent字符串的一部分列出:


#Fields: c-ip cs-username cs-method cs-uri-query sc-status cs(User-Agent)
192.168.234.32 admin HEAD /test/basic - 401 Mozilla/3.0+(Compatible);Brutus/AET
192.168.234.32 test HEAD /test/basic - 401 Mozilla/3.0+(Compatible);Brutus/AET
192.168.234.32 root HEAD /test/basic - 401 Mozilla/3.0+(Compatible);Brutus/AET

验证错误被写入安全事件日志,所以我们建议定期监控,以发现可能的暴力攻击。验证错误时发生的不同类型日志的更多细节,请参见本章结尾处的附加链接。图4-3显示了基本验证密码猜测攻击之后典型的日志事件。

窃听和重放攻击

任何在网络传输中暴露凭据的验证协议都可能遭到窃听攻击,窃听攻击又被称为嗅探攻击(Sniffing attack),这个叫法来自于网络协议分析器的口头称呼(Sniffer)。重放攻击一般在窃听的基础上构建,是指攻击者使用捕获的凭据仿冒有效用户的身份。

遗憾的是,一些最流行的Web验证协议确实在网上暴露凭据。我们将在接下来的小节中讨论对流行Web验证协议的常见攻击。

基本验证: 我们已经看到HTTP基本验证容易遭到密码猜测攻击。现在我们谈谈这个协议的另一个弱点。为了阐述我们的观点,首先为你介绍一点基本验证工作原理的背景知识。

图4-3对Windows IIS的密码猜测尝试导致这些事件写入安全日志

基本验证从客户没有提供任何验证凭据的情况下,向Web服务器提交一个对受保护资源的请求开始。在响应中,服务器将以一个拒绝访问信息作为响应,包含请求基本验证凭据的WWW-Authenticate首部。大部分Web浏览器包含通过提示用户输入用户名和密码自动处理这种请求的例程,如图4-4所示。注意,这是浏览器实例化的一个独立操作系统窗口,而不是HTML表单。

图4-4 Web浏览器提示用户基本验证

用户输入密码之后,浏览器重新发出请求,这次带有验证凭据。下面是原始HTTP(为了简洁而进行了编辑)中的一个典型基本验证交换。首先,这是对使用基本验证进行安全保护的资源的原始请求:


GET /test/secure HTTP/1.0

服务器以包含一个WWW-Authenticate:Basic首部的HTTP 401 Unauthorized(需要验证)消息应答:


HTTP/1.1 401 Unauthorized
WWW-Authenticate: Basic realm="luxor"

这导致客户端浏览器弹出一个类似图4-4的窗口。用户在窗口中输入用户名和密码并且点击OK,通过HTTP发送:


GET /test/secure HTTP/1.0
Authorization: Basic dGVzdDp0ZXN0

注意,客户端实际上只是重新发送相同请求,这次带有一个Authorization首部。如果凭据不正确,服务器再次响应“未授权”信息、指向请求资源的重定向或者资源本身,这取决于服务器实现。

等一下——用户名和密码在哪里?按照基本验证规范,验证凭据在客户端响应中的Authorization首部中传送,使用Base64算法编码。对Base64不熟悉的人们乍一看可能以为是某种加密,因为编码方式难以理解。但是,因为Base64是一种编码,使用任何可用的实用程序或者脚本语言就可以很简单地解码这些编码值。这里提供一个示例Perl脚本,来说明操纵Base64有多么容易:


#!/usr/bin/perl
# bd64.pl
# decode from base 64
use MIME::Base64;
print decode_base64($ARGV[0]);

我们在前面的基本验证示例中看到的编码值上运行这个bd64.pl解码器:


C:\bd64.pl dGVzdDp0ZXN0
test:test

你能看到,尽管Authorization首部中发送的值有谜一般的特性,基本验证仍为窃听攻击大开方便之门,这是该协议最致命的局限。使用HTTPS时,情况略有好转。但是,与基本验证关联的客户端风险仍然存在,因为没有不活跃超时或者在不关闭浏览器的情况下注销的机制。

摘要: 摘要验证在RFC2617中描述,设计用于提供比基本验证更高级别的安全性。摘要验证基于质询-响应验证模式。这种技术常用于证明某人知道一个秘密,但是不需要此人通过暴露在窃听攻击中的不安全信道发送这个秘密。

摘要验证的工作方式与基本验证类似。用户发起一个不带验证凭据的请求,Web服务器用指明需要凭据访问请求资源的WWWAuthenticate首部响应。但是,服务器不发送和基本验证一样的Base64编码用户名和密码,而是询问客户一个随机值(称为Nonce)。然后浏览器使用单向加密函数创建用户名、密码、给定随机值、HTTP方法以及请求的URI的消息摘要。消息摘要函数又称散列算法(Hashing algorithm),是在一个方向上很容易计算,而在另一个方向上不可能计算的加密函数。这种散列方法与基本验证所用的可以简单解码的Base64编码形成了对比。服务器质询中可以指定任何散列算法,RFC 2617描述中默认的算法是MD5散列函数。

为什么要有随机数?为什么不直接计算用户密码的Hash?与其他加密协议中随机数的用处有所不同,摘要验证中随机数的使用与密码方案中加入的“盐”相似。它用于创建更大的密钥空间,使得对常见密码进行数据库或者预先计算攻击更加困难。考虑一下能够存储字典中所有单词的MD5 hash值的大型数据库和少于10个字母数字字符的所有排列。攻击者只要计算MD5 hash一次,后面就可以对数据库作一次查询来寻找与MD5 hash关联的密码。使用随机值有效地增加了密钥空间,存储许多用户的预先计算Hash密码需要比原来大得多的数据库,这使数据库攻击变得低效。

摘要验证对于基本验证有显著的改进,主要是因为网上没有传递明文的验证凭据。这使得摘要验证比基本验证更能抵御窃听攻击。但是,摘要验证仍然容易遭到重放攻击,因为即使缺少用户的实际密码,响应中的消息摘要都将对请求资源进行授权。但是,因为原始资源请求包含在消息摘要中,重放攻击只能允许访问特定的资源(假定摘要验证已经正确地实现)。

RFC 2617中描述了其他针对摘要验证的可能攻击。

注意  Microsoft的摘要验证实现要求服务器具有明文版本的用户密码的访问权,才能计算摘要。因此,在Windows上实现摘要验证需要用户密码使用可逆加密存储,而不是使用标准的单向MD4算法。

对于喜欢修补的人,下面是一个使用来自Neil Winton的Digest::MD5 Perl模块的简短脚本,可以生成MD5 hash:


#!/usr/bin/perl
# md5-encode.pl
# encode using MD5
use Digest::MD5 qw(md5_hex);
print md5_hex($ARGV[0]);

这个脚本以16进制格式输出MD5 hash,但是你也可以在第4行的合适位置替换为qw(md5)或qw(md5_base64),输出二进制或者Base64。这个脚本可以为比较摘要验证字符串和已知值(比如破解)提供一个初级工具,但是除非用户名、随机值、HTTP方法以及请求的URI都已知,否则这种努力可能颗粒无收。

Gregory Duchemin提供了一个有趣的MD5 hash破解工具MDcrack(链接参见本章结尾处的“参考与延伸阅读”)。

窃听对策

使用128位SSL加密能够阻碍这些攻击,强烈建议所有使用基本验证和摘要验证的网站使用这种加密。

为了对抗重放攻击,摘要随机值可以从难以伪造的信息中建立,比如客户端IP地址和时间戳的摘要。

基于表单验证攻击

与我们目前讨论过的机制相比,基于表单的验证不依赖于基本Web协议如HTTP支持的功能(如基本验证或者摘要验证)。这是一种高度自定义的使用表单的验证机制,表单通常由带有FORM标记和描述输入域的INPUT标记的HTML组成,用户通过输入域输入用户名和密码。用户凭据通过HTTP或者HTTPS发送之后,由某些服务器端逻辑进行评估,如果有效,返回给用户某种具有足够长度、复杂性和随机性的令牌,供用户在以后的请求中使用。因为高度可自定义和灵活的特性,基于表单的验证可能是互联网上最流行的验证技术。但是,因为它不依赖标准化的HTTP验证规范,也就没有执行基于表单验证的标准化方式。

现在介绍一个简单的基于表单验证示例,借以阐述其基本原则。虽然这个示例为了简单而基于Microsoft ASP.NET表单验证,但是我们将说明通用于所有表单验证类型的关键点。下面是示例的场景:你有一个包含文件default.aspx的目录,这个文件访问前需要表单验证。为了实现ASP.NET表单验证,需要两个其他文件:这个目录(或者应用根目录)中的web.config文件,以及取得用户名/密码输入的登录表单(称作login.aspx)。login.aspx指定受到表单验证保护的资源,并包含用户名和密码的列表,可用于查询验证用户在login.aspx中输入的凭据。当然,可以使用任何用户名/密码信息来源——例如,SQL数据库。建议存储加入“盐”的密码Hash来代替原始密码,以降低暴露密码的风险,并且加大基于字典的攻击的难度。下面是有人请求default.aspx时所发生的事情:


GET /default.aspx HTTP/1.0

因为web.config文件指定这个目录中的所有资源需要表单验证,服务器以HTTP 302重定向到登录页面——login.aspx作为响应:


HTTP/1.1 302 Found
Location: /login.aspx?ReturnUrl=%2fdefault.aspx

客户现在看到login.aspx表单,如图4-5所示。

这个表单包含一个隐含域“state”,以及两个可见的域:取得用户名输入的“txtUser”域和取得密码输入的“txtPassword”域。这些域都用HTML INPUT标记实现。用户输入用户名和密码并点击Login按钮,将表单数据(包含隐含域)传回给服务器:


POST /login.aspx?ReturnUrl=%2fDefault.aspx HTTP/1.0
STATE=gibberish&txtUser=test&txtPassword=test

图4-5 ASP.NET实现的一个标准登录表单

应该始终使用POST方法来代替GET动作发送用户名和密码,但是两种动作所完成的工作一样。偏爱POST的原因是避免在客户端(浏览器历史中)、缓冲中继设备如代理等、以及远程应用服务器上验证凭据的不安全存储,因为这些系统常常由于统计或者性能的原因缓冲或者记录HTTP GET数据。这些普通的机制可能导致存储在GET请求中的用户验证凭据因为疏忽而暴露给未授权用户。

注意,正如这里所看到的情况,除非实施SSL,否则穿越网络的凭据是明文形式。服务器接收凭据数据并且用web.config中(同样,这可以是任何自定义的数据存储)的用户名/密码进行验证。如果凭据匹配,服务器将返回“HTTP 302 Found”和一个将客户重定向回原来请求的资源(default.aspx)的Location首部,以及包含验证令牌的一个Set-Cookie首部:


HTTP/1.1 302 Found
Location: /Default.aspx
Set-Cookie: AuthCookie=45F68E1F33159A9158etc.; path=/
htmlheadtitleObject moved/title/headbody

注意,这里的cookie用3DES加密,这可以在ASP.NET的web.config文件中指定。现在客户重新请求原始资源default.aspx,使用自动附加到HTTP首部的新设验证令牌(cookie):


GET /Default.aspx HTTP/1.0
Cookie: AuthCookie=45F68E1F33159A9158etc

服务器验证cookie有效,然后用HTTP 200 OK消息提供资源。所有301和302重定向都在后台透明进行,并不通知最终用户。最终结果:用户请求资源,要求提供用户名/密码,如果输入正确的凭据将接收到资源(不正确则得到自定义的错误页面)。应用可能选择提供一个“注销”按钮,当用户点击时删除cookie。cookie也可以设置为在一定的时间内过期,这时服务器认为它不再有效(例如不活跃或者最大会话长度超时)。

同样,这一示例使用特殊的端到端技术——ASP.NET表单验证来演示表单验证的基本原理。可以使用任何其他相似的技术或者技术组合获得同样的结果。

和目前讨论的其他验证技术相似,基于表单的验证也是密码猜测攻击的目标。我们喜欢使用Brutus(本章前面已作介绍)来攻击基于表单的验证,主要是因为它的Modify Sequence(修改顺序)|Learn Form Settings(学习表单设置)功能。这种功能允许用户指定指向登录表单的URL,Brutus自动分析出用户名、密码和表单支持的其他域(包括隐含域)。图4-6展示了HTML表单翻译器。

图4-6 Brutus的HTML表单翻译器解析一个登录表单,高亮显示域用于后续攻击

Brutus还使你能指定成功验证时期望从登录表单得到的响应。这种能力很重要,因为表单验证的高度可自定义特性,网站常常会为成功和不成功的登录实现独特的响应页面。使用Brutus工具,你可以自定义密码猜测为特定的目标网站使用的任何响应。显然,如果验证信道没有用HTTPS或者其他加密协议加密,基于表单的验证也很容易遭到窃听和重放攻击。

基于表单验证几乎总是使用会话cookie临时存储验证令牌,所以用户访问网站不需要在每次请求的时候重复提供验证凭据。会话cookie只存储在内存中,和存储在磁盘并且在不同会话中维持的持续性cookie相反。cookie有时候可能被操纵或者完全被窃取,如果没有加密,可能泄露不合适的信息(注意,在我们的示例中ASP.NET配置为使用3DES加密cookie)。更多关于攻击cookie的内容参见第5章。

Secure和HTTPOnly这两个cookie属性标志标志在发出包含敏感信息的会话或者持续性cookie时很重要(理想状态下,敏感信息绝不应该存储在cookie中,如果需要,这些信息始终应该加密)。当发出的cookie带有secure标志时,支持这个属性的客户浏览器绝不会从非HTTPS安全信道上发送这个cookie。HTTPOnly标志最早由Microsoft创建,这是保护用户免遭针对应用cookie中敏感数据的会话劫持和数据渗漏攻击的一次正确的尝试。支持HTTPOnly的客户浏览器将不允许JavaScript访问相应cookie中的数据,即使这种访问符合同源策略。HTTPOnly被认为是阻止会话ID和其他敏感值因恶意脚本注入攻击(例如XSS)而轻易泄露的一种破损安全手段。但是,一旦攻击者能够在目标应用中执行恶意脚本,不管是否可以直接访问会话cookie,都能自由地在受害用户的安全上下文中执行任何操作。通常,这可以通过创建一系列执行敏感功能的后台异步请求(XmlHttpRequest)来实现。尽管安全社区中对这一保护机制的整体有效性存在争议,但是鼓励开发人员尽可能使用这一功能,作为应用防御中的附加层次。也就是说,应用开发人员优先考虑的应该总是首先去除导致恶意脚本注入攻击的输入校验漏洞。更多关于secure和HTTPOnly cookie属性的内容可以在本章结尾处的“参考与延伸阅读”中找到。

一些应用开发人员错误地臆测表单“隐含”的HTML输入域中对用户隐藏的数据对最终用户来说不可见。他们可能会粗心地将敏感的验证凭据或者其他数据放入这些域,而不是依赖基于cookie的会话ID验证用户的特定事务。虽然这种情况不经常出现,但是应用安全评估人员应该训练自己密切注意存储在隐含域中的数据类型。

绕过SQL支持的登录表单: 在执行具备SQL后端的基于表单验证的网站上,SQL注入可能被用来绕过验证(关于SQL注入技术的更具体细节参见第6章)。许多网站使用数据库存储密码并且使用SQL查询数据库来验证凭据。典型的SQL语句看上去如下所示(这个示例因为页宽约束而分为两行):


SELECT * from AUTHENTICATIONTABLE WHERE Username = 'username input' AND
Password = 'password input'

如果输入校验没有正确实现,在用户名域注入


Username' --

将使SQL语句变成:


SELECT * from AUTHENTICATIONTABLE WHERE Username = 'Username'
--AND Password = 'password input'

SQL语句最后的破折号指定余下的SQL语句是应该被忽略的注释。这条语句等价于:


SELECT * from AUTHENTICATIONTABLE WHERE Username = 'Username'

这就对了!密码检查像变魔术般地被删除了!

这种通用攻击和许多对基于表单验证的攻击一样,不需要根据网站做太多定制。我们已经发现了地下黑客组织自动化这种攻击的工具。

为了将攻击提高一个层次,SQL注入也可以在密码域上进行。假定使用相同的SQL语句,使用密码:


DUMMYPASSWORD' OR 1 = 1 --

将得到下面的SQL语句(这个示例因为页宽约束而分为两行):


SELECT * from AUTHENTICATIONTABLE WHERE Username = 'Username'
AND Password = 'DUMMYPASSWORD' OR 1 = 1 -- '

在语句SQL语句的最后加上OR 1=1将总是得到真(True)的结果,验证再次被绕过。

在2001年中期,许多Web验证软件包被发现有相似的问题。Apache mod_auth_mysql、oracle、pgsql和pgsql_sys构建SQL查询,没有检查单引号(这些漏洞在德国斯图加特大学的CERT公告中进行了描述;连接参见本章结尾处的“参考与延伸阅读”)。

绕过LDAP支持的登录表单: 不是所有应用都集成了具有后端SQL数据库服务器的验证组件。许多Web应用,尤其是在集团内部网中的应用,使用基于轻量目录访问协议(LDAP)的服务器来提供相似的验证能力。如果编码不安全,这些应用可能暴露出LDAP注入漏洞,这种漏洞可能被利用来绕过验证控制。虽然用于利用这些漏洞的语法与SQL注入不同,但是基本概念相同。关于LDAP注入攻击的更多信息在本书第6章中可以看到,有兴趣的读者可以参考该章获得进一步信息。

绕过XML支持的登录表单: 虽然不如SQL支持和LDAP支持验证那么常见,仍有一些应用依赖于静态的XML文件存储应用用户数据和登录凭据。和SQL及LDAP一样,不能正确验证用户所提供凭据的应用可能暴露漏洞,使攻击者能绕过常规的验证控制。经典的例子是使用验证期间提供的用户名构建XPath查询,从后端XML文档查询相关记录的应用。如果用户名中在XPath查询中具有特殊意义的字符没有被正确验证,攻击者就可能修改查询返回任意记录,而不管是否提供正确密码。更具体的XML和XPath注入的示例可在第7章中找到。

基于表单验证攻击的对策

我们前面讨论的密码猜测、窃听和重放攻击的对策对基于表单验证同样是适用的。

预防SQL注入和其他注入攻击的最佳方法是执行输入校验(参见第6章)以及使用参数化的SQL查询或者参数化存储过程。应该执行输入校验以确保用户名不包含无效的字符。HTML标记字符、空白和特殊字符如!、$、%等应该尽可能禁止。使用存储过程时应该注意安全编码,这样就不会简单地将SQL注入漏洞从应用移到数据库过程。一般的规则是,开发人员应该避免使用动态构造的SQL查询,特别是这些查询包含了用户提供的输入时。

预防XML和LDAP注入攻击通过强输入校验来完成,这种校验避免使用在两种技术中具有特殊意义的字符。在不可能完全禁止使用这些特殊字符时,必须特别小心,在执行依靠后端数据存储的验证时,正确地脱离验证凭据,使用合适的API。

我们还要给出一个忠告,确保Web应用使用的所有软件包都应用最新的补丁,更新到最新版本。让表单避开针对你所定制的代码的攻击是一回事,当你的免费或者商业验证软件包出现相同问题时就完全是另一回事了。