攻击验证机制

从概念上讲,验证机制是Web应用程序所有安全机制中最简单的一种机制。通常,应从用程序必须核实用户提交的用户名和密码正确与否。如果正确,就允许用户登录,否则就禁止用户登录。

验证机制也是应用程序防御恶意攻击的中心机制。它处在防御未授权访问的最前沿,如果用户能够突破那些防御,他们通常能够控制应用程序的全部功能,自由访问其中保存的数据。缺乏安全稳定的验证机制,其他核心安全机制(如会话管理和访问控制)都无法有效实施。

设计一个安全的验证机制看似简单,实际上却是一件极其麻烦的事情。在现实世界中,Web应用程序验证机制通常是最薄弱的环节,由此攻击者能够获得未授权访问。我们曾见过无数应用程序由于验证机制存在各种缺陷而被攻破的实例。

本章将详细介绍困扰Web应用程序的大量设计和执行缺陷。这些缺陷之所以存在,主要是因为应用程序设计者和开发者无法回答一个简单的问题:攻击者针对验证机制实施攻击能够实现什么目标?在绝大多数情况下,只要认真分析一下某个应用程序,就可以发现许多潜在的漏洞,其中任何一个都足以破坏应用程序。

许多最常见的验证漏洞实际上极其简单。任何人都可以在登录表单中输入字典中的单词,试图猜测有效的密码。另外,应用程序中也隐藏着一些细微的缺陷,只有对复杂的多阶段登录机制进行仔细分析后才能发现它们并对其加以利用。本章将全面描述这些攻击,包括那些成功突破一些最安全、防御最稳健的Web应用程序的验证机制的技巧。

验证技术

当执行验证机制时,Web应用程序开发者可以采用各种不同的技术:

基于HTML表单的验证;

多元机制,如组合型密码和物理令牌;

客户端SSL证书或智能卡;

HTTP基本和摘要验证;

使用NTLM或Kerberos整合Windows的验证;

验证服务。

到目前为止,Web应用程序中最常用的验证机制是使用HTML表单获取用户名和密码,并将它们提交给应用程序。因特网上90%以上的应用程序都采用这种机制。

在更加注重安全的因特网应用程序(如电子银行)中,这种基本的机制通常扩展到几个阶段,要求用户提交其他证书,如PIN号码或从机密字中选择的字符。HTML表单仍主要用于获取相关数据。

最为注重安全的应用程序(如为进行巨额交易的个人提供服务的私人银行)通常采用使用物理令牌的多元机制。这些令牌通常产生一组一次性口令,或者基于应用程序指定的输入执行一个质询-响应功能。随着这种技术的成本日渐降低,可能会有更多应用程序采用这种机制。但是,许多这类解决方案实际上无法解决它们旨在解决的威胁,主要是钓鱼攻击和使用客户端木马的威胁。

一些Web应用程序使用客户端SSL证书或在智能卡中执行加密机制。但是,由于管理和分配这些项目的成本非常高昂,通常只有那些用户不多的安全极其重要的应用程序才会使用它们。

因特网上的应用程序很少使用基于HTTP的验证机制(基本、摘要和整合Windows的机制),企业内联网更常采用这种机制。这时,组织内部用户提供标准的网络或域证书,应用程序通过以上一种技术对其进行处理,再允许用户访问企业应用程序。

一些应用程序还采用Microsoft Passport之类的第三方验证服务,但暂时这种机制尚未得到大量使用。

大多数与验证有关的漏洞和攻击适用于上面提到的任何一种技术。由于绝大多数应用程序普遍采用基于HTML表单的验证,我们将描述每一种与其有关的特殊漏洞和攻击,以及与其他可用技术有关的主要差异和攻击方法。

验证机制设计缺陷

与Web应用程序常用的任何其他安全机制相比,验证功能中存在着更多设计方面的薄弱环节。即使在基于用户名和密码验证用户这种非常简单的标准化模型中,其中包含的设计缺陷也容易导致应用程序被非法访问。

密码保密性不强

许多Web应用程序没有或很少对用户密码的强度进行控制。应用程序常常使用下列形式的密码:

非常短或空白的密码;

以常用的字典词汇或名称为密码;

密码和用户名完全相同;

仍然使用默认密码。

图6-1是一个实施脆弱密码强度规则的实例。通常,终端用户很少具有安全意识。因此,没有实施严格密码标准的应用程序很可能包含大量使用脆弱密码的用户账户。攻击者很容易就可猜测出这些密码,从而对应用程序进行未授权访问。

图6-1 -个实施脆弱密码强度规则的应用程序

渗透测试步骤

设法查明任何与密码强度有关的规则。

(1)浏览该Web站点,查找任何描述上述规则的内容。

(2)如果可以进行自我注册,用不同种类的脆弱密码注册几个账户,了解应用程序采用何种规则。

(3)如果拥有一个账户并且可以更改密码,试着把密码更改为各种脆弱密码。

 注解 如果应用程序仅通过客户端控件实施密码强度规则,这本身并不是一个安全问题,因为普通用户仍然受到保护。虽然诡计多端的攻击者可为自己分配脆弱密码,但这通常并不会给应用程序造成威胁。

尝试访问

http://mdsec.net/auth/217/

蛮力攻击登录

登录功能的公开性往往诱使攻击者试图猜测用户名和密码,从而获得未授权访问应用程序的权力。如果应用程序允许攻击者使用不同的密码重复进行登录尝试,直到找到正确的密码,那么它就非常容易遭受攻击,因为即使是业余攻击者也可以在浏览器中手动输入一些常见的用户名和密码。

最近一些知名站点沦陷,成千上万个现实世界中的密码也随之泄漏,这些密码或者以明文形式存储,或者使用可蛮力攻击的散列存储。现实世界中的一些最常见的密码如下所示:

password

网站名称

12345678

qwerty

abc123

111111

monkey

12345

letmein

 注解 管理员密码实际上比密码策略允许的更为脆弱。它们可能在实施密码策略之前就已设置,或者通过其他应用程序或界面设置。

在这种情况下,精明的攻击者会根据冗长的常用密码列表,使用自动技巧尝试猜测出密码。依赖今天的带宽和处理能力,通过普通PC和DSL连接,攻击者每分钟就可以发出数千个登录尝试。这样,即使最强大的密码最终也会被攻破。

我们将在第14章详细描述实施蛮力登录的各种自动技巧和工具。使用Burp Intruder对一个账户成功实施密码猜测攻击的过程如图6-2所示。我们可通过HTTP响应码、响应长度及缺乏“登录错误”消息等差异清楚区分成功的登录尝试。

图6-2 成功实施密码猜测的攻击示例

一些应用程序使用客户端控件防止密码猜测攻击。例如,某个应用程序可能会设置cookie failedlogins = 1,如果登录尝试失败,递增这个值。达到某个上限后,服务器将在提交的cookie中检测这个值,并拒绝处理登录尝试。这种客户端防御可防止仅使用浏览器实施的手动攻击,但如第5章所述,这种防御可轻易避开。

如果登录失败计数器保存在当前会话中,这时就会出现前一个漏洞的变化形式。虽然在客户端并没有表明该漏洞存在的任何迹象,但攻击者只需要获得一个全新的会话(例如,通过保留会话cookie)即可继续实施密码猜测攻击。

最后,在某些情况下,应用程序会在失败的登录尝试达到一定次数后锁定目标账户。但是,它会通过表明(或允许攻击者推测)所提交的密码是否正确的消息,对随后的登录尝试作出响应。这意味着,即使目标账户被锁定,攻击者仍然可以完成密码猜测攻击。如果应用程序在一段时间后自动解除账户的锁定状态,则攻击者只需要等到这一时刻,然后即可使用发现的密码正常登录。

渗透测试步骤

(1)用控制的某个账户手动提交几个错误的登录尝试,监控接收到的错误消息。

(2)如果应用程序在大约10次登录失败后还没有返回任何有关账户锁定(account lockout)的消息,再尝试正确登录。如果登录成功,应用程序可能并未采用任何账户锁定策略。

(3)如果账户被锁定,可以尝试重复使用不同的账户。如果应用程序发布任何cookie,这次可以将每个cookie仅用于一次登录尝试,并为随后的每次登录尝试获取新cookie。

(4)此外,如果账户被锁定,应查看与提交无效密码相比,提交有效密码是否会导致应用程序的行为出现任何差异。如果确实如此,则可以继续实施密码猜测攻击,即使账户被锁定。

(5)如果没有控制任何账户,尝试枚举一个有效的用户名(参阅6.2.3节)并使用它提交几次错误登录,监控有关账户锁定的错误消息。

(6)发动蛮力攻击前,首先确定应用程序响应成功与失败登录之间的行为差异,以此分清它们在自动攻击过程中表现出的区别。

(7)列出已枚举出的或常见的用户名列表和常用密码列表。根据所获得的任何有关密码强度规则的信息对上述列表加以修改,以避免进行多余测试。

(8)使用这些用户名和密码的各种排列组合,通过适当的工具或定制脚本迅速生成登录请求。监控服务器响应以确定成功的登录尝试。我们将在第14章详细说明使用自动化方法实施定制攻击的各种技巧和工具。

(9)如果一次针对几个用户名,通常最好以广度优先(breadth-first)而非深度优先(depth-first)的方式实施这种蛮力攻击。这包括循环使用一组密码(从最常用的密码开始)并轮流对每个用户名使用每一个密码。这种方法有两方面的好处:首先,可以更加迅速地确定使用常用密码的账户;其次,这样做可以降低触发任何账户锁定防御的可能性,因为在使用同一个账户进行连续登录之间存在时间延迟。

尝试访问

http://mdsec.net/auth/16/

http://mdsec.net/auth/32/

http://mdsec.net/auth/46/

http://mdsec.net/auth/49/

详细的失败消息

一个典型的登录表单要求用户输入两组信息(用户名和密码),而另外一些应用程序则需要更多信息(如出生日期、纪念地或PIN号码)。

如果登录尝试失败,当然可以得出结论:至少有一组信息出错。但是,如果应用程序通知是哪一组信息无效,就可以利用它显著降低登录机制的防御效能。

在最简单的情况下,如果只需要用户名和密码登录,应用程序可能会通过指出失败的原因(用户名无效或密码错误)来响应失败的登录尝试,如图6-3所示。

图6-3 详细的登录失败消息指出已猜测出有效的用户名

在这种情况下,攻击者可以发动一次自动化攻击,遍历大量常见的用户名,确定哪些有效。当然,用户名一般并非秘密(例如,登录时并不隐藏用户名)。但是,如果攻击者能够轻易确定有效的用户名,就更可能在有限的时间内、运用一定的技能、付出一定的精力攻破应用程序,并将枚举出的用户名列表作为随后各种攻击的基础,包括密码猜测、攻击用户数据或会话,或者社会工程[1]。

除主要的登录功能外,还可以对验证机制的其他组件进行用户名枚举。理论上,需要提交真实或潜在用户名的任何功能都可用于这一目的。例如,通常都可以对用户注册功能进行用户名枚举。如果应用程序允许新用户注册并指定他们自己的用户名,由于应用程序需要防止注册重复用户名,在这种情况下,几乎不可能阻止用户名枚举攻击。如本章后面部分所述,有时也可以对密码修改或忘记密码功能进行用户名枚举。

 注解 许多验证机制以隐含或明确的方式提示用户名。根据设计常识,Web邮件账户的用户名通常为电子邮件地址。许多其他站点在应用程序中透露用户名,或者允许使用可轻易猜测出的用户名(如user1842,User1843等),并未考虑攻击者会对其加以利用的情况。

在更复杂的登录机制中,应用程序要求用户提交几组信息,或者完成几个步骤。这时,详细的失败消息或差异点可帮助攻击者轮流针对登录过程的每个阶段发动攻击,提高其获得未授权访问的可能性。

 注解 这种漏洞可能会以更隐含的形式出现。即使响应有效和无效用户名的错误消息表面看来完全相同,它们之间仍然存在细微的差别,可用于枚举有效的用户名。例如,如果应用程序中的多条代码路径返回“相同的”失败消息,这些消息之间仍然可能存在细小的排版差异。有些时候,应用程序响应在屏幕上显示的内容完全相同,但其HTML源代码可能隐藏着细微的区别,如注释或布局方面的不同。如果无法轻易枚举出有效的用户名,应当仔细比较应用程序对有效和无效用户名作出的响应。

可以使用Burp Suite中的“比较”(Comparer)工具自动分析并突出显示两个应用程序响应之间的差异,如图6-4所示。这有助于迅速确定有效的用户名是否会导致应用程序的响应出现任何系统性的差异。

图6-4 使用Burp Suite确定应用程序响应的细微差别

渗透测试步骤

(1)如果已经知道一个有效的用户名(例如一个受控制的账户),使用这个用户名和一个错误的密码进行一次登录,然后使用一个完全随机的用户名进行另一次登录。

(2)记录服务器响应两次登录尝试的每一个细节,包括状态码、任何重定向、屏幕上显示的信息以及任何隐藏在HTML页面中的差异。使用拦截代理服务器保存服务器上来回流量的完整历史记录。

(3)努力找出服务器响应两次登录尝试的任何明显或细微的差异。

(4)如果无法发现任何差异,在应用程序中任何提交用户名的地方(例如自我注册、密码修改与忘记密码功能)重复上述操作。

(5)如果发现服务器响应有效和无效用户名之间的差异,收集一个常见用户名列表并使用一个定制脚本或自动工具迅速提交每个用户名,过滤出说明用户名有效的响应(请参阅第14章了解相关内容)。

(6)开始枚举操作之前,请确定应用程序是否在登录尝试失败次数达到一定数目后执行账户锁定(请参阅6.2.2节)。如果应用程序执行账户锁定,最好在设计枚举攻击时记住这一点。例如,如果应用程序只允许登录某个账户时失败3次,可能就会在使用通过自动枚举发现的每个用户名登录时“浪费” 一次登录机会。因此,当进行枚举攻击时,不要在每次登录时提交完全不合理的密码,而是提交常见的密码,如password1或以用户名为密码。如果应用程序执行脆弱的密码强度规则,在枚举操作过程中执行的一些登录尝试就很可能会取得成功,有些情况下还可能同时查明用户名和密码。要以用户名设置密码字段,可以使用Burp Intrude冲的“破城槌”(battering ram)攻击模式,在登录请求的几个位置插入相同的有效载荷。

即使应用程序对包含有效与无效用户名登录尝试的响应完全相同,我们仍然可以根据应用程序响应登录请求的时间枚举出用户名。应用程序通常依据登录请求是否包含有效用户名,对其进行截然不同的后端处理。例如,如果登录请求中包含一个有效的用户名,应用程序可能会从后端数据库中获取用户资料,对这些资料进行各种处理(如检查账户是否到期),然后确认密码(可能使用一个资源密集型散列算法),如果密码错误返回一条常规消息。仅仅使用浏览器可能无法检测出应用程序处理两个请求之间的时间差异,但自动工具能够区分这种差异。即使这种操作会产生大量错误警报,但100个用户名约50%的有效率仍然要强于10 000个用户名仅0.5%的有效率。第15章将详细讨论如何检测并利用这种时间差异从应用程序中提取信息。

 提示 除登录功能外,我们还可以从其他地方获取有效的用户名。检查在应用程序解析过程中(请参阅第4章了解相关内容)发现的所有源代码注释,确定所有明显的用户名。开发者或组织内部内其他人员的电子邮件地址都可能为有效的用户名;任何可访问的日志功能也可能透露用户名。

尝试访问

http://mdsec.net/auth/53/

http://mdsec.net/auth/59/

http://mdsec.net/auth/70/

http://mdsec.net/auth/81/

http: //mdsec.net/auth/167/

证书传输易受攻击

如果应用程序使用非加密的HTTP连接传输登录证书,处于网络适当位置的窃听者当然就能够拦截这些证书。根据用户的位置,窃听者可能位于:

用户的本地网络中;

用户的IT部门内;

用户的ISP内;

因特网骨干网上;

托管应用程序的ISP内;

管理应用程序的IT部门内。

 注解 上述任何一个位置可能由授权用户占用,也可能由通过其他方法攻破相关基础架构的外部攻击者占用。即使某一特定网络的中间媒介可信,最好还是使用安全的传输机制传送敏感数据。

即使是通过HTTPS登录,如果应用程序处理证书的方式并不安全,证书仍有可能被泄露给未授权方。

如果以查询字符串参数、而不是在POST请求主体中传送证书,许多地方都可能记录这些证书,例如用户的浏览器历史记录中、Web服务器日志内以及主机基础架构采用的任何反向代理中。如果攻击者成功攻破这些资源,就能够获取保存在这些地方的用户证书,从而提升其访问权限。

虽然大多数Web应用程序确实使用POST请求主体提交HTML登录表单,但令人奇怪的是,应用程序常常通过重定向到一个不同的URL来处理登录请求,而以查询字符串参数的形式提交证书。我们并不清楚应用程序开发者为何采用这种方法,但以连接一个URL的302重定向执行请求,比使用另一个通过JavaScript提交的HTML表单提出POST请求要容易得多。

Web应用程序有时将用户证书保存在cookie中,通常是为了执行设计不佳的登录、密码修改、“记住我”等机制。攻击者通过攻击用户cookie即可获取这些证书。如果cookie相对安全可靠,可通过访问客户端的本地文件系统获得它们。即使证书被加密,攻击者仍然不需要用户证书就可以通过重新传送cookie实施登录。第12章和第13章将描述攻击者如何通过各种方法获取其他用户的cookie。

许多应用程序对应用程序中未经验证的区域使用HTTP,而在登录时转而使用HTTPS。如果是这样,应在向浏览器加载登录页面时转换到HTTPS,使得用户能够在输入证书前核实页面是否真实可信。但是,一些应用程序通常使用HTTP加载登录页面,而在提交证书时才转换到HTTPS。这样做是不安全的,因为用户不能核实登录页面的真实性,因此无法保证安全提交证书。那么,处在适当位置的攻击者就可以拦截并修改登录页面,更改登录表单的目标URL以使用HTTP。等到精明的用户意识到证书已使用HTTP提交时,攻击者已成功获取这些证书。

渗透测试步骤

(1)进行一次成功登录,监控客户端与服务器之间的所有来回流量。

(2)确定在来回方向传输证书的每一种情况。可以在拦截代理服务器中设置拦截规则,标记包含特殊字符串的消息(请参阅第20章了解相关内容)。

(3)如果发现通过URL查询字符串或者以cookie的方式提交证书,或者由服务器向客户端传输证书的任何情况,了解传输的一切细节并设法弄清应用程序开发者这样做的目的。设法查明攻击者干扰应用程序逻辑以获取其他用户证书的各种手段。

(4)如果通过非加密渠道传输任何敏感信息,这样做当然容易遭受攻击。

(5)如果没有发现证书传输不安全的情况,留意任何明显被编码或模糊处理的数据。如果这些数据中包括敏感数据,其模糊算法可能遭受逆向工程。

(6)如果使用HTTPS提交证书,但使用HTTP加载登录表单,那么应用程序就容易遭受中间人攻击,攻击者也可能使用这种攻击手段获取证书。

尝试访问

http://mdsec.net/auth/88/

http://mdsec.net/auth/90/

http://mdsec.net/auth/97/

密码修改功能

令人奇怪的是,许多Web应用程序并不允许用户修改其密码。但是,出于两个方面的原因,精心设计的验证机制需要这种功能。

定期强制修改密码可降低某一密码成为密码猜测攻击目标的可能性,同时降低攻击者不需要检测即可使用被攻破密码的可能性,由此降低密码被攻击的概率。

怀疑自己的密码已被攻破的用户需要立即修改密码,以降低未授权使用概率。虽然密码修改功能是一个高效验证机制的必要组成部分,但从设计来看,它往往易于遭受攻击。在主要登录功能中特意避免的漏洞通常在密码修改功能中重复出现。许多Web应用程序的密码修改功能不需要验证即可访问,并为攻击者提供某些信息或允许攻击者执行某些操作。

提供详细的错误消息,说明被请求的用户名是否有效。

允许攻击者无限制猜测“现有密码”字段。

在验证现有密码后,仅检查“新密码”与“确认新密码”字段的值是否相同,允许攻击者不需入侵即可成功查明现有密码。

典型的密码修改功能通常包含一个相对较大的逻辑判定树。应用程序需要确认用户、验证提供的现有密码、集成任何账户锁定防御、对提交的新密码进行相互比较并根据密码强度规则进行比较,以及以适当的方式向用户返回任何错误条件。为此,密码修改功能通常包含难以察觉的可用于破坏整个机制的逻辑缺陷。

渗透测试步骤

(1)确定应用程序中的所有密码修改功能。即使公布的内容(published content)中没有明确的密码修改功能链接,应用程序仍然可能实施这种功能。我们已在第4章中说明了发现应用程序中隐藏内容的各种技巧。

(2)使用无效的用户名、无效的现有密码及不匹配的“新密码”和“确认新密码”值向密码修改功能提交各种请求。

(3)设法确定任何可用于用户名枚举或蛮力攻击的行为(如6.2.2节和6.2.3节所述)。

 提示 如果密码修改表单只可由验证用户访问,且其中并无用户名字段,表单中仍有可能包含一个任意用户名。表单可能将用户名保存在一个可被轻易修改的隐藏字段中。如果在字段中没有发现用户名,设法使用和主登录表单中相同的参数提交另一个包含用户名的参数。这种技巧有时可成功覆盖当前用户的用户名,使攻击者能够向其他用户的证书发动蛮力攻击,即使在主登录页面不可能实施这种攻击。

尝试访问

http://mdsec.net/auth/104/

http://mdsec.net/auth/117/

http://mdsec.net/auth/120/

http://mdsec.net/auth/125/

http://mdsec.net/auth/129/

http://mdsec.net/auth/135/

忘记密码功能

与密码修改功能一样,重新获得忘记密码的机制常常会引入已在主要登录功能中避免的问题,如用户名枚举。

除这种缺陷外,忘记密码功能设计方面的缺点往往使它成为应用程序总体验证逻辑中最薄弱的环节。下面介绍几种常见的设计缺点。

忘记密码功能常常向用户提出一个次要质询以代替主要登录功能,如图6-5所示。与试图猜测用户密码相比,响应这种质询对攻击者来说更容易一些。母亲的娘家姓、纪念日、最喜欢的颜色等问题的答案要比可能的密码的数量少得多。而且,这些问题的答案常常隐藏在公开的信息中,意志坚定的攻击者无须花费多大精力即可找到答案。

图6-5 账户恢复功能中的次要质询

许多时候,应用程序允许用户在注册阶段设定他们自己的密码恢复质询与响应,而用户很有可能会设置极其不安全的质询,这也许是因为用户错误地认为应用程序仅向他们自己提出这些质询,例如:“我拥有一只船吗?”在这种情况下,希望获得访问权的攻击者可使用自动攻击手段遍历一组已枚举的或常见的用户名,记录所有密码恢复质询,并选择那些看似最容易猜测出的质询发动攻击。(请参阅第14章了解有关如何在自定义攻击中获取这类数据的技巧。)

与密码修改功能一样,即使应用程序开发者在主登录页面阻止攻击者向密码恢复质询的响应发动蛮力攻击,他们也往往会在忘记密码功能中忽略这种攻击的可能性。如果应用程序允许无限制地回答密码恢复质询,那么意志坚定的攻击者就很可能会攻破这个密码。

一些应用程序使用一个简单的密码“暗示”(可由用户在注册阶段配置)代替恢复质询。由于用户错误地认为只有自己才会看到这些暗示,他们往往设置非常明显的暗示,甚至是和密码完全相同的暗示。此外,拥有一组常见或已枚举出的用户名的攻击者可轻易获取大量密码暗示,然后开始实施猜测。

在用户正确响应一个质询后,应用程序即允许用户重新控制他们的账户,这种机制非常容易遭受攻击。执行这种机制的一个相对安全的方法是向用户在注册阶段提供的电子邮件地址发送一个唯一的、无法猜测的、存在时间限制的恢复URL。用户在几分钟内访问这个URL即可设置一个新密码。但是,我们常常会遇到其他一些在设计上存在缺陷的账户恢复机制。

 一些应用程序在用户成功响应一个质询后即向其透露现有与遗忘的密码,使攻击者能够无限制地使用该账户,而不会被账户所有者检测出来。即使账户所有者随后修改被攻破的密码,攻击者只需重新回答相同的质询即可获得新密码。

 一些应用程序在用户成功完成一个质询后,立即让其进入一个不需验证的会话。这同样使攻击者可无限制地使用该账户,而不会被账户所有者检测出来,甚至不需要知道用户的密码。

 一些应用程序采用发送一个唯一恢复URL的机制,但却将这个URL发送至用户在完成质询时指定的电子邮件地址中。除能够记录攻击者所使用的电子邮件地址外,这种方法根本无法提高恢复过程的安全性。

 提示 即使应用程序并未提供一个在屏幕上显示的字段,要求用户输入接收恢复URL的电子邮件地址,它仍有可能通过一个隐藏表单字段或cookie传送这个地址。攻击者因此获得双重机会:一方面,可以发现所攻破的用户的电子邮件地址;另一方面,可对这个地址进行修改,用自选的地址接收恢复URL。

 一些应用程序允许用户在成功完成一个质询后直接重新设置密码,并且不向用户发送任何电子邮件通知。这意味着直到所有者碰巧再次登录时才会注意到账户被攻击者攻破;而且,如果所有者认为自己一定是忘记了密码,于是用上述方法重新设置密码,他可能仍然无法发觉账户已被攻破。那么,只是希望偶尔访问应用程序的攻击者就可以在一段时间攻破一个用户账户,在另一段时间攻破另一个不同用户的账户,从而继续无限制地使用该应用程序。

渗透测试步骤

(1)确定应用程序中的所有忘记密码功能。即使公布的内容中没有明确的忘记密码功能链接,应用程序仍然可能实施这种功能(请参阅第4章了解相关内容)。

(2)使用受控制的账户执行一次完整的密码恢复过程,了解忘记密码功能的工作机制。

(3)如果恢复机制使用质询,确定用户是否能够设定或选择他们自己的质询与响应。如果用户可设定或选择自己的质询与响应,使用一组已枚举的或常见的用户名获取一些质询,并对其进行分析,找出任何非常容易猜测出响应的质询。

(4)如果恢复机制使用密码“暗示”,采取和上个步骤相同的操作获得一组密码暗示,并对任何可轻易猜测出答案的暗示发动攻击。

(5)设法确定忘记密码机制中任何可用于用户名枚举或蛮力攻击的行为(详情请参阅上文)。

(6)如果应用程序在忘记密码请求的响应中生成一封包含恢复URL的电子邮件,获取大量这类URL,并试图确定任何可帮助预测向其他用户发布URL的模式。请使用和分析会话令牌以实现预测相同的技巧(请参阅第7章了解相关内容)。

尝试访问

http://mdsec.net/auth/142/

http://mdsec.net/auth/145/

http://mdsec.net/auth/151/

记住我功能

为方便用户,避免他们每次在一台特定的计算机上使用应用程序时需要重复输入用户名和密码,应用程序通常执行“记住我”功能。这些功能在设计上并不安全,致使用户易于遭受本地和其他计算机用户的攻击。

一些“记住我”功能通过一个简单的cookie执行,如RememberUser=pet erwiener(见图6-6)。向初始应用程序页面提交这个cookie时,应用程序信任该cookie,认为其属于通过验证的用户,并为该用户建立一个应用程序会话,从而避开登录过程。攻击者可以使用一组常见或已枚举出的用户名,不需要任何验证即可完全访问应用程序。

图6-6 一个易受攻击的“记住我”功能

一些“记住我”功能设置一个cookie,其中并不包含用户名,而是使用一个持久会话标识符,例如RememberUser=1328。向登录页面提交这个标识符时,应用程序查询与其相关的用户,并为该用户建立一个应用程序会话。和普通会话令牌一样,如果可预测或推断出其他用户的会话标识符,攻击者就可以遍历大量可能的标识符,找到与应用程序用户相关联的标识符,不经验证即可访问他们的账户。请参阅第7章了解实施这种攻击的有关技巧。

即使cookie中保存的用于重新识别用户的信息得到适当保护(如被加密),以防止其他用户对此进行推断或猜测,但攻击者通过跨站点脚本之类的漏洞或本地访问用户的计算机依然可以轻易获得这些信息(请参阅第12章了解相关内容)。

渗透测试步骤

(1)激活所有“记住我”功能,确定应用程序是否完全“记住”用户名和密码,还是仅记住用户名,仍然要求用户在随后的访问中输入密码。如果采用后一种设置,该功能就不大可能存在安全漏洞。

(2)仔细检查应用程序设定的所有持久性cookie,以及其他本地存储机制中的持久性数据,如IE的userData、Seilverlight的隔离存储、Flash的本地共享对象。寻找其中保存的任何明确标识出用户或明显包含可预测的用户标识符的数据。

(3)即使其中保存的数据经过严密编码或模糊处理,仔细分析这些数据,并比较“记住”几个非常类似的用户名或密码的结果,找到任何可对原始数据进行逆向工程的机会。在这里可使用将在第7章描述的用于检测会话令牌意义和模式的相同技巧。

(4)试图修改持久性cookie的内容,并设法让应用程序确信:另一名用户已经将其资料保存在你的计算机中。

尝试访问

http://mdsec.net/auth/219/

http://mdsec.net/auth/224/

http://mdsec.net/auth/227/

http://mdsec.net/auth/229/

http://mdsec.net/auth/232/

http://mdsec.net/auth/236/

http://mdsec.net/auth/239/

http://mdsec.net/auth/245/

用户伪装功能

一些应用程序允许特权用户伪装成其他用户,以在该用户的权限下访问数据和执行操作。例如,一些银行应用程序允许服务台操作员口头验证一名电话用户,然后将银行的应用程序会话转换到该用户的权限下,以为其提供帮助。

伪装功能一般存在各种设计缺陷。

伪装功能可以通过“隐藏”功能的形式执行,不受常规访问控制管理。例如,任何知道或猜测出URL/admin/ImpersonateUser.jsp的人都能够利用该功能伪装成任何其他用户(请参阅第8章了解相关内容)。

当判定用户是否进行伪装时,应用程序可能会信任由用户控制的数据。例如,除有效会话令牌外,用户可能还会提交一个指定其会话当前所使用的账户的cookie。攻击者可以修改这个值,不需验证即可通过其他用户的账户访问应用程序,如图6-7所示。

图6-7 一种易受攻击的用户伪装功能

如果应用程序允许管理用户被伪装,那么伪装逻辑中存在的任何缺陷都可能导致垂直权限提升漏洞。攻击者不仅可以访问其他普通用户的数据,甚至可以完全控制应用程序。

某种伪装功能能够以简单“后门”密码的形式执行,该密码可和任何用户名一起向标准登录页面提交,以作为该用户进行验证。由于许多原因,这种设计非常危险,但攻击者所获得的最大好处是:他们可在实施标准攻击(如对登录机制进行蛮力攻击)的过程中发现这个密码。如果后门密码在用户的真实密码前得到匹配,那么攻击者就可能发现后门密码功能,从而访问每一名用户的账户。同样,一次蛮力攻击可能导致两个不同的“触点”,因而揭示后门密码,如图6-8所示。

图6-8 一次密码猜测攻击出现两个“触点”,说明应用程序使用后门密码

渗透测试步骤

(1)确定应用程序中的所有伪装功能。即使公布的内容中没有明确的伪装功能链接,应用程序仍然可能实施这种功能(请参阅第4章了解相关内容)。

(2)尝试使用伪装功能直接伪装成其他用户。

(3)设法操纵任何由伪装功能处理的用户提交的数据,尝试伪装成其他用户。特别留意任何不通过正常登录页面提交用户名的情况。

(4)如果能够成功利用伪装功能,尝试伪装成任何已知的或猜测出的管理用户,以提升用户权限。

(5)实施密码猜测攻击(请参阅6.2.3节)时,查明是否有用户使用多个有效密码,或者某个特殊的密码是否与几个用户名匹配。另外,用在蛮力攻击中获得的证书以许多不同的用户登录,检查是否一切正常。特别注意任何“以X登录”的状态消息。

尝试访问

http://mdsec.net/auth/272/

http://mdsec.net/auth/290/

证书确认不完善

精心设计的验证机制强制要求密码满足各种要求,如最小密码长度和同时使用大小写字符。相应地,一些设计不佳的验证机制不仅没有强制执行这些最佳实践,而且对用户遵守这些要求的愿望置之不理。

例如,一些应用程序截短密码,只确认前n个字符;一些应用程序并不对密码进行大小写检查;一些应用程序在检查密码之前删除不常用的字符(有时以执行输入确认为借口)。最近,一些相当有名的应用程序都被确认具有此类行为,一些好奇用户的试验和错误致使人们发现了这一问题。

以上这些密码确认限制可显著减少可能的密码数量。通过实验,渗透测试员可以判定一个密码是否得到完全确认,或者某个限制是否生效。然后就可以针对登录机制的自动攻击方法进行调整,删除不必要的测试,大量减少攻破用户账户所需提交的请求的数量。

渗透测试步骤

(1)使用一个现有的账户,尝试用密码的各种变化形式进行登录:删除最后一个字符、改变字符大小写、删除任何特殊排版的字符。如果其中一些尝试取得成功,继续实验过程,尝试了解完整的证书确认过程。

(2)利用得到的所有结果调整自动密码猜测攻击,删除多余的测试,提高成功的几率。

尝试访问

http://mdsec.net/auth/293/

非唯一性用户名

一些支持自我注册的应用程序允许用户指定他们自己的用户名,而且并不强制要求用户使用唯一的用户名。虽然这种应用程序极其少见,但我们还是见到过若干这类应用程序。

由于两方面的原因,这种设计存在一些缺陷。

在注册阶段或随后修改密码的过程中,共享同一个用户名的两个用户可能碰巧选择相同的密码。如果出现这种情况,应用程序要么拒绝第二名用户选择的密码,要么允许两个账户使用相同的证书。如果属于前者,应用程序将会向一名用户泄露另一名用户的证书;如果属于后者,其中一名用户登录后会访问另一名用户的账户。

即使由于登录失败尝试次数方面的限制,在其他地方不可能实施这种攻击,攻击者仍然可以利用这种行为成功实施蛮力攻击。攻击者可以使用不同的密码,多次用一个特殊的用户名注册,同时监控说明使用该用户名和密码的账户已经存在的不同响应。攻击者不需以目标用户进行任何一次登录尝试,即可获取该用户的密码。

设计存在缺陷的自我注册功能还可能造成用户枚举漏洞。如果应用程序禁止使用相同的用户名,那么攻击者可以注册大量常见的用户名,从而确定遭到拒绝的现有用户名。

渗透测试步骤

(1)如果应用程序允许自我注册,尝试用不同的密码两次注册同一个用户名。

(2)如果应用程序阻止第二次注册企图,也可以利用这种行为枚举现有的用户名,虽然在主登录页面或其他地方不可能这样做。用一组常见的用户名进行多次注册尝试,设法确定被应用程序阻止的已注册用户名。

(3)如果可成功注册完全相同的用户名,尝试用相同的密码注册两个相同的用户名,以此确定应用程序的行为。

(a)如果以上做法得到错误消息,也可以利用这种行为实施一次蛮力攻击,虽然在主登录页面不可能实施这种攻击。针对一个枚举或猜测出的用户名发动攻击,尝试用一组常用密码多次注册这个用户名。如果应用程序拒绝某个特殊的密码,就可以发现目标账户的现有密码。

(b)如果没有得到错误消息,使用指定的证书登录,看看出现什么结果。可能需要注册几个用户,修改每个账户保存的不同数据,以确定这种行为是否可用于未授权访问其他用户的账户。

可预测的用户名

一些应用程序根据某种可以预测的顺序(如cust5331、cust5332)自动生成账户用户名。如果应用程序以这种方式运转,弄清了用户名顺序的攻击者就可以很快获得全部有效用户名,以此作为后续攻击的基础。与依赖不断提交由词汇驱动请求的枚举方法不同,这种确定用户名的方法不需实施入侵,也很少给应用程序造成干扰。

渗透测试步骤

(1)如果用户名由应用程序生成,设法获得几个连续的用户名,看能否从中看出任何顺序或模式。

(2)如果存在某种顺序或模式,向后推断列出所有可能的有效用户名。这种方法可作为需要有效用户名的登录蛮力攻击和其他攻击的基础,如利用访问控制漏洞(请参阅第8章了解相关内容)。

尝试访问

http://mdsec.net/auth/169/

可预测的初始密码

一些应用程序一次性或大批量创建用户,并自动指定初始密码,然后以某种方式将密码分配给所有用户。这种生成密码的方式可让攻击者能够预测其他应用程序用户的密码。基于内联网的企业应用程序常常存在这种漏洞。例如,应用程序为每位雇员创建一个账户,并向其发送一份打印好的密码通知。

如果所有用户收到相同的密码,或者根据其用户名或工作职能创建的密码,这种密码最容易被攻破。另外,生成的密码可能包含某种顺序,攻击者查看少数几个初始密码样本即可确定或猜测出其他用户的密码。

渗透测试步骤

(1)如果密码由应用程序生成,设法获得几个连续的密码,看能否从中看出任何顺序或模式。

(2)如果存在某种顺序或模式,根据这种模式推断,获取其他应用程序用户的密码。

(3)如果密码呈现出一种可能与用户名相联系的模式,可以设法使用已知或猜测出的用户名与相应推断出的密码进行登录。

(4)其他情况下,可以使用推断出的密码列表作为利用一组枚举出的用户名或常见用户名实施蛮力攻击的基础。

尝试访问

http://mdsec.net/auth/172/

证书分配不安全

许多应用程序并不在用户与应用程序正常交互的过程中分配新建账户的证书(如通过邮寄或电子邮件)。有时,采用这种分配方式主要出于安全考虑,例如,确保用户提供的邮寄或电子邮件地址属于其本人。

这种分配方式有时会带来安全风险。例如,如果分配证书的邮件中同时包含用户名和密码,没有给邮件设置使用时间限制,没有要求用户在第一次登录时修改密码,那么,大多数甚至是绝大部分应用程序用户都不会修改初始证书,并且将收到的邮件保存很长一段时间,而未授权方有可能在此期间访问这些分配证书的邮件。

有时应用程序并不分配证书,而是传送一个“账户激活”URL,用户通过它设置自己的初始密码。如果发送给连续用户的URL表现出某种顺序,攻击者就可以通过注册几个紧密相连的用户确定这种顺序,以此推断出发送给最近与后续用户的激活URL。

某些Web应用程序表现的一种相关行为是,允许新用户以看似安全的方式注册账户,然后向每个新用户发送一封包含其完整的登录证书的欢迎电子邮件。最糟糕的情况是,具有安全意识的用户决定立即修改可能已被攻破的密码,但随后又收到一封电子邮件,其中包含“以备日后参考”的新密码。这种行为相当奇怪,并且完全没有必要,因此,我们强烈建议用户停止使用表现出此类行为的Web应用程序。

渗透测试步骤

(1)获得一个新账户。如果应用程序并不要求在注册阶段设置所有证书,弄清应用程序如何向新用户分配证书。

(2)如果应用程序使用账户激活URL,设法注册几个紧密相连的新账户,确定收到的URL中的任何顺序。如果确定某种模式,尝试预测应用程序发送给最近与后续用户的激活URL,尝试使用这些URL占有他们的账户。

(3)尝试多次重复使用同一个激活URL,看看应用程序是否允许这样做。如果遭到拒绝,尝试在重复使用URL之前锁定目标账户,看看现在这种方法是否可行。

验证机制执行缺陷

由于在执行过程中存在错误,即使精心设计的验证机制也可能非常不安全。这些错误可能导致信息泄露、完全避开登录,或者使验证机制的总体安全弱化。与保密性不强的密码和可被蛮力攻击之类的设计缺陷相比,执行缺陷往往更加细微,更难以发现。由于大量威胁模型和渗透测试可能已经发现了最为注重安全的应用程序中的任何明显的执行缺陷,针对这类缺陷实施攻击通常会取得更大的成果。我们曾在某大型银行所采用的Web应用程序中发现以下所述的执行缺陷。

故障开放登录机制

故障开放逻辑是一种逻辑缺陷(将在第11章详细描述),如果验证机制中出现这种缺陷,就会造成十分严重的后果。

下面是一个精心设计的故障开放登录机制实例。如果由于某种原因,调用db.getUser()产生异常(例如,因为用户的请求中没有用户名或密码参数而出现空指针异常),用户仍然可以成功登录。虽然产生的会话可能并不属于某个特殊的用户,因此无法执行其全部功能,但攻击者仍然可以通过这种方法访问一些敏感数据或功能。

实际上,我们不能指望这样的代码通过即使是最简单的安全审查。但是,在更复杂的机制中很可能存在概念相同的缺陷。这些机制会产生大量分层方式调用,可能会出现许多潜在的错误并在不同的位置对它们进行处理,其中更复杂的确认逻辑可能需要维护重要的登录进展状态。

渗透测试步骤

(1)使用控制的一个账户执行一次完整、有效的登录。使用拦截代理服务器记录提交的每一份数据、收到的每一个响应。

(2)多次重复登录过程,以非常规方式修改提交的数据。例如,对于客户端传送的每个请求参数或cookie:

(a)提交一个空字符串值;

(b)完全删除名/值对;

(c)提交非常长和非常短的值;

(d)提交字符串代替数字或相反;

(e)以相同和不同的值,多次提交同一个数据项。

(3)仔细检查应用程序对提交的每个畸形请求的响应,确定任何不同于基本情况的差异。

(4)根据这些观察结果调整测试过程。如果某个修改造成行为改变,设法将这个修改与其他更改组合在一起,使应用程序的逻辑达到最大限度。

尝试访问

http://mdsec.net/auth/300/

多阶段登录机制中的缺陷

一些应用程序使用精心设计的多阶段登录机制,例如:

输入用户名和密码;

响应一个质询,答案是PIN中的特殊数字或一个值得纪念的词;

提交在不断变化的物理令牌上显示的某个值。

多阶段登录机制旨在提高基于用户名和密码的简单登录模型的安全性。通常,多阶段登录机制首先要求用户通过用户名或类似数据项确认自己的身份;随后,登录阶段再执行各种验证检查。这种机制常常存在安全漏洞,特别是各种逻辑缺陷(请参阅第11章了解相关内容)。

 错误观点 人们常常认为多阶段登录机制比标准的用户名/密码验证的安全漏洞更少。这种看法是错误的。执行多次验证检查可能会显著提高登录机制的安全性。但相应地,这个过程也存在更多的执行缺陷。如果一个多阶段登录机制存在多个执行缺陷,它甚至还没有基于用户名和密码的正常登录安全。

在执行过程中,一些多阶段登录机制对用户与早先阶段的交互做出潜在不安全的假设,如下所示。

应用程序可能认为访问第三阶段的用户已经完成第一、二阶段的验证。因此,它可能允许直接由第一阶段进入第三阶段并且提供正确证书的攻击者通过验证,使仅拥有部分正常登录所需的各种证书的攻击者能够成功登录。

应用程序可能会信任由第二阶段处理的一些数据,因为这些数据已经在第一阶段得到确认。但是,攻击者能够在第二阶段操控这些数据,提供一个不同于第一阶段的值。例如,在第一阶段,应用程序会判定用户的账户是否已经过期、被锁定或者属于管理用户,或者是否需要完成第二阶段以外的其他登录阶段。如果攻击者能在不同登录阶段的转换过程中干扰这些标记,他们就可以更改应用程序的行为,让他们只需部分证书即可通过验证,或者提升其权限。

应用程序可能认为每个阶段的用户身份不会发生变化,因此,它并不在每个阶段明确确认用户身份。例如,第一阶段可能需要提交一个有效的用户名和密码,第二阶段需要重新提交用户名(此时保存在隐藏表单字段中)和不断变化的物理令牌上的一个值。如果攻击者在每个阶段提交有效的数据对,但这些数据属于两个不同的用户,那么应用程序可能会允许该用户通过验证,认为他是两名用户中的任意一名用户。这就允许拥有自己物理令牌并发现其他用户密码的攻击者能够以该用户的身份登录(反之亦然)。虽然不对其他信息加以利用,攻击者无法完全攻破登录机制,但它的总体安全状态已严重削弱,应用程序为执行二元机制所投入的大量开支和努力并未取得预期的效果。

渗透测试步骤

(1)使用控制的一个账户执行一次完整、有效的登录。使用拦截代理服务器记录向应用程序提交的每一份数据。

(2)确定各个不同登录阶段以及在每个阶段收集到的数据。确定是否不止一次收到某条信息,或者是否有信息被返回给客户端,并通过隐藏表单字段、cookie或者预先设置的URL参数重新提交(请参阅第5章了解相关内容)。

(3)使用各种畸形请求多次重复登录过程:

(a)尝试按不同的顺序完成登录步骤;

(b)尝试直接进入任何特定的阶段,从那里继续登录;

(c)尝试省略每个阶段并从下一阶段继续登录;

(d)运用想象力,想出其他开发者无法预料的方式访问不同的登录阶段。

(4)如果有数据不止提交一次,尝试在另一个阶段提交一个不同的值,看看是否仍然能够成功登录。有些提交数据可能是多余的,实际上并不由应用程序处理。有些数据在某个阶段得到确认,随后就被应用程序所信任。在这种情况下,尝试在一个阶段提供一名用户的证书,然后在下一阶段转换成由另一名用户进行验证。应用程序可能在几个阶段都对同一个数据进行确认,但执行不同的检查。在这种情况下,尝试在第一个阶段提供(例如)一名用户的用户名和密码,然后在第二个阶段提供另一名用户的用户名和PIN号码。

(5)特别注意任何通过客户端传送、并不由用户直接输入的数据。应用程序可能使用它们保存登录进展状态信息,并且信任这些数据。例如,如果第三个阶段的请求中包含参数stage2complete=true,那么攻击者就可以通过设置这个值直接进入第三个阶段。尝试修改应用程序提交的值,确定是否可以使用这种方法进入或省略登录阶段。

尝试访问

http://mdsec.net/auth/195/

http://mdsec.net/auth/199/

http://mdsec.net/auth/203/

http://mdsec.net/auth/206/

http://mdsec.net/auth/211/

一些登录机制在其中一个登录阶段提出一个随机变化的问题。例如,提交用户名和密码后,应用程序会向用户提出许多“机密”问题中的一个(关于用户母亲的娘家姓、出生地、小学名称等),或者要求其提交一个机密短语中的两个随机字母。采用这种做法的基本原理在于:即使攻击者截获了用户在某个时候输入的全部信息,他也无法在其他时刻作为该用户登录,因为这时应用程序将提出不同的问题。

在某些执行过程中,这种功能会遭到破坏,因而无法实现其目的。

应用程序可能会提出一个随机选择的问题,把有关问题的细节保存在隐藏的HTML表单字段中,而不是服务器上。随后用户提交该问题及其答案。这样,攻击者就能够选择回答哪个问题,允许他们截获用户在某个时候的输入后,重复使用截获的信息进行登录。

应用程序可能会对每个登录尝试提出一个随机选择的问题,但如果某个用户无法回答该问题,它并不记住向该用户提出了什么问题。如果该用户稍后又提交一次登录尝试,应用程序又生成另一个随机问题。这允许攻击者遍历所有问题,直到收到他们知道答案的那个问题,从而利用在某个时候截获的用户输入重复进行登录。

 注解 上面的第二种情况确实相当微妙,因此,许多现实中的应用程序都易于受到攻击。初看上去,要求用户回答一个值得纪念的词中的两个字母的应用程序似乎能够正常运转,增强登录机制的安全。但是,如果每次在通过前一个验证阶段后随机选择两个字母,那么截获用户在某个时候的登录信息的攻击者只需重复进入这个验证阶段,直到应用程序要求提交他知道的两个字母为止,这样做也不存在任何账户锁定风险。

渗透测试步骤

(1)如果一个登录阶段使用一个随机变化的问题,确定问题本身是否和回答一起提交。如果是这样,改变这个问题并提交正确答案,看看是否仍然能够成功登录。

(2)如果应用程序并不允许攻击者提交任意问题和答案,用同一个账户进行部分登录,每次进行到出现不同的问题为止。如果每次都出现不同的问题,那么攻击者仍然能够选择回答哪个问题。

尝试访问

http://mdsec.net/auth/178/

http://mdsec.net/auth/182/

 注解 在一些登录组件随机变化的应用程序中,应用程序在一个阶段收集用户的全部证书。例如,主登录页面中可能显示一个表单,其中包含用户名、密码和一个机密问题字段,且当每次加载登录页面时,机密问题都会发生改变。在这种情况下,机密问题的随机性根本无法阻止已经截获一名用户在某个时候的输入信息的攻击者重新传送有效的登录请求,也无法修改登录过程以实现这种目的,因为攻击者只需重复加载登录页面,直到找到他知道答案的问题。在另一种类似的情况下,应用程序可能会设置一个持久性cookie,“确保”向特定用户提出相同的问题,直到该用户正确回答这个问题。当然,攻击者只需修改或删除这个cookie就能够轻易避开这种防御措施。

不安全的证书存储

如果应用程序以不安全的方式存储登录证书,那么,即使验证过程本身并不存在缺陷,登录机制的安全也会被削弱。

Web应用程序常常以危险的方式将用户证书存储在数据库中,这包括以明文形式存储密码。但是,即使使用MD5或SHA-1等标准算法对密码进行散列处理,攻击者仍然可以在预先计算的散列值数据库中查找观察到的散列。因为应用程序使用的数据库账户必须能够随时读/写这些证书,攻击者可以利用应用程序中的许多其他漏洞访问这些证书,例如,命令、SQL注入漏洞(参阅第9章)或访问控制漏洞(参阅第8章)。

尝试访问

一些在线数据库的常见散列函数可从以下网址查看:

http://passcracking.com/index.php

http://authsecu.com/decrypter-dechiffrer-cracker-hash-md5/script-hash-md5.php

渗透测试步骤

(1)分析应用程序中所有与验证有关的功能以及任何与用户维护有关的功能。如果发现任何向客户端返回用户密码的情况,即表明应用程序并未以安全的方式保存密码,或者密码以明文方式呈现,或应用程序使用了可还原加密形式保存密码。

(2)如果发现应用程序中存在任何一种任意命令或查询执行漏洞,设法确定应用程序将用户证书保存在数据库或文件系统的什么位置。

(a)找到这些位置,弄清应用程序是否以非加密形式保存密码。

(b)如果以散列形式存储密码,则应检查表明账户分配有常用或默认密码,以及散列并未经过“加salt”处理的非唯一值。

(c)如果使用标准算法以“不加salt的散列”形式存储密码,则应查询在线散列数据库,以确定对应的明文密码值。

保障验证机制的安全

执行安全的验证解决方案需要同时满足几个关键安全目标,许多时候也需要牺牲其他目标,如功能、易用性和总成本。有些时候,“更加”安全实际上可能适得其反。例如,强迫用户设置超长密码并频繁修改密码往往促使他们将密码记录下来(因而导致密码泄露)。

鉴于验证漏洞的多样性,以及应用程序需要采取非常复杂的防御措施以减轻所有这些漏洞的危害,许多应用程序设计者与开发者选择接受某些威胁,以集中精力阻止最严重的攻击。在实现这种防御平衡的过程中,我们需要考虑以下因素。

应用程序所提供功能的安全程度。

用户对不同类型的验证控制的容忍和接受程度。

支持一个不够友好的用户界面系统所需的成本。

竞争性解决方案相对于应用程序可能产生的收入方面的金融成本或它所保护资产的价值。

我们将在本节说明阻止各种针对验证机制攻击的最有效方法,然后让读者自行决定哪种防御措施最适合他们的特殊需求。

使用可靠的证书

应强制执行适当的最小密码强度要求。这些要求包括:最小密码长度,使用字母、数字和排版字符,同时使用大、小写字符,避免使用字典中的单词、名称和其他常见密码,避免以用户名为密码,避免使用和以前的密码相似或完全相同的密码。和大多数安全措施一样,不同的密码强度要求适用于不同类型的用户。

应使用唯一的用户名。

系统生成的任何用户名和密码应具有足够的随机性,其中不包含任何顺序,即使攻击者访问大量连续生成的实例也无法对其进行预测。

允许用户设置足够强大的密码。例如,应允许其设置长密码,允许在密码中使用各种类型的字符。

安全处理证书

应以不会造成非授权泄露的方式创建、保存和传送所有证书。

应使用公认的加密技术(如SSL)保护客户端与服务器间的所有通信。既无必要也不需要使用定制解决方案保护传输中的数据。

如果认为最好在应用程序的不需验证的区域使用HTTP,必须保证使用HTTPS加载登录表单,而不是在提交登录信息时才转换到HTTPS。

只能使用POST请求向服务器传输证书。绝不能将证书放在URL参数或cookie中(即使临时放置也不行)。绝不能将证书返还给客户端,即使是通过重定向参数传送也不行。

所有服务器—客户端应用程序组件应这样保存证书:即使攻击者能够访问应用程序数据库中存储的所有相关数据,他们也无法轻易恢复证书的原始值。达到这种目的最常用的方法是使用强大的散列函数(如至本书截稿时的SHA-256函数),并对其进行“加salt处理”以降低预先计算的离线攻击(precomputed offline attack)的危害。该salt应特定于拥有密码的账户,以防止攻击者重播或替换散列值。

一般来说,客户端“记住我”功能应仅记忆如用户名之类的非保密数据。在安全要求较低的应用程序中,可适当允许用户选择一种工具来记住密码。在这种情况下,客户端不应保存明文证书(应使用密钥以可逆加密的形式保存密码,且只有服务器知道这个密钥);并向用户警告直接访问他们的计算机或远程攻破他们计算机的攻击者可能造成的风险。应特别注意消除应用程序中存在的可用于盗窃其中保存的证书的跨站点脚本漏洞(请参阅第12章了解相关内容)。

应使用一种密码修改工具(请参阅6.4.6节),要求用户定期修改其密码。

如果以非正常交互的形式向新建账户分配证书,应以尽可能安全的形式传送证书,并设置时间限制,要求用户在第一次登录时更改证书,并告诉用户在初次使用后销毁通信渠道。

应考虑在适当的地方使用下拉菜单而非文本字段截取用户的一些登录信息(如值得纪念的词中的一个字母)。这样做可防止安装在用户计算机上的键盘记录器截获他们提交的所有数据。(但是,还请注意,简单的键盘记录器只是攻击者用于截获用户输入的一种手段。如果攻击者已经攻破用户的计算机,那么从理论上讲,他就能够记录计算机上发生的各种类型的事件,包括鼠标活动、通过HTTPS提交的表单以及截屏。)

正确确认证书

应确认完整的密码。也就是说,区分大小写,不过滤或修改任何字符,也不截短密码。

应用程序应在登录处理过程中主动防御无法预料的事件。例如,根据所使用的开发语言,应用程序应对所有API调用使用“全捕获”型异常处理程序(catch-all exception handler)。这些程序应明确删除用于控制登录状态的所有会话和方法内部数据(method-local data),并使当前会话完全失效。因此,即使攻击者以某种方式避开验证,也会被服务器强制退出。

应对验证逻辑的伪代码和实际的应用程序源代码进行仔细的代码审查,以确定故障开放条件之类的逻辑错误。

如果应用程序执行支持用户伪装功能,应严格控制这种功能,以防止攻击者滥用它获得未授权访问。鉴于这种功能的危险程度,通常有必要从面向公众的应用程序中彻底删除该功能,只对内部管理用户开放该功能,而且他们使用伪装也应接受严格控制与审核。

应对多阶段登录进行严格控制,以防止攻击者破坏登录阶段之间的转换与关系。

 有关登录阶段进展和前面验证任务结果的所有数据应保存在服务器端会话对象中,绝不可传送给客户端或由其读取。

 禁止用户多次提交一项登录信息;禁止用户修改已经被收集或确认的数据。如果需要在几个阶段使用同一个数据(如用户名),应在第一次收集时将该数据保存在会话变量中,随后从此处引用该数据。

 在每一个登录阶段,应首先核实前面的阶段均已顺利完成。如果发现前面的阶段没有完成,应立即将验证尝试标记为恶意尝试。

 为防止泄露的是哪个登录阶段失败(攻击者可利用它轮流针对每个阶段发动攻击)的信息,即使用户无法正确完成前面的阶段、即使最初的用户名无效,应用程序也应总是处理完所有的登录阶段。在处理完所有的登录阶段后,应用程序应在最后阶段结束时呈现一条常规“登录失败”消息,并且不提供失败位置的任何信息。

如果在登录过程中需要回答一个随机变化的问题,请确保攻击者无法选择回答问题。

 总是采用一个多阶段登录过程,在第一阶段确认用户身份,并在后面的阶段向用户提出随机变化的问题。

 如果已向某一用户提出一个特定的问题,将该问题保存在永久性用户资料中,确保每次该用户尝试登录时向其提出相同的问题,直到该用户正确回答这个问题。

 如果向某个用户提出一个随机变化的质询,将提出的问题保存在服务器端会话变量而非HTML表单的隐藏字段中,并根据保存的问题核实用户随后提供的答案。

 注解 以上详细介绍了设计一个安全验证机制的微妙之处。提出一个随机变化的问题时稍不谨慎就可能给攻击者提供用户名枚举的机会。例如,为防止攻击者选择回答他知道答案的问题,应用程序可能会将该用户提出的最后一个问题保存在用户资料中,并不断提出该问题直到得到正确答案。这样,使用相同用户名多次登录的攻击者就会遇到相同的问题。但是,如果攻击者使用一个无效的用户名进行相同的操作,应用程序处理的方法可能会有所不同:由于没有与无效用户名有关的用户资料,也没有问题被保存起来,因此,应用程序将提出一个不同的问题。攻击者可以利用这种在多次登录尝试中表现出来的行为差异,推断某个特殊用户名的有效性。在一次自定义攻击中,攻击者能够迅速获得大量用户名。

如果应用程序希望防御这种可能性,它必须采取一些预防措施。如果收到使用无效用户名发起的登录尝试,应用程序必须在某个位置记录向这个无效用户名提出的随机问题,并确保随后使用这个用户名登录都会遇到相同的问题。更进一步,应用程序可定期更换到一个不同的问题,模拟不存在的用户已作为正常用户登录,导致提出的下一个问题出现变化。但是,从某种意义上说,应用程序设计者必须做出让步,因为挫败意志如此坚定的攻击者几乎是不可能的。

防止信息泄露

应用程序使用的各种验证机制不应通过公开的消息,或者通过从应用程序的其他行为进行推断,来揭示关于验证参数的任何信息。攻击者应无法判定是提交的哪个数据造成了问题。

应由单独一个代码组件使用一条常规消息负责响应所有失败的登录尝试。这样做可避免由不同代码路径返回的本应不包含大量信息的消息,因为消息排版方面的差异、不同的HTTP状态码、其他隐藏在HTML中的信息等内容而让攻击者看出差别,从而产生一个细微的漏洞。

如果应用程序实行某种账户锁定以防止蛮力攻击(如6.4.5节所述),应小心处理以防造成信息泄露。例如,如果应用程序透露,由于Y次失败登录,已将某个特殊的账户冻结X分钟,这种行为就可被用于枚举有效的用户名。另外,明确公开账户锁定策略标准也使攻击者能够调整任何登录尝试,不顾锁定政策继续猜测密码。为避免用户名枚举,如果从相同浏览器发出一系列失败的登录尝试,应用程序应通过一条常规消息提出警告:如果出现多次登录失败,账户将被冻结,并建议用户稍后再试。可通过使用一个cookie或隐藏字段追踪来自相同浏览器的重复登录失败,从而达到上述目的。(当然,不应使用这种机制实行任何实际的安全控制,仅用于为努力回忆其证书的普通用户提供帮助。)

如果应用程序支持自我注册,那么它能够以两种方式防止这种功能被用于枚举现有用户名。

 不允许自我选择用户名,应用程序可为每个新用户建立一个唯一(和无法预测)的用户名,防止应用程序披露表明一个选定的用户名已经存在的信息。

 应用程序可以使用电子邮件地址作为用户名。如果是这样,应用程序会在登录过程的第一个阶段要求用户输入他们的电子邮件地址,然后告诉他们等待接收一封电子邮件,按照其中的指示操作。如果电子邮件地址已经被注册,应用程序会在电子邮件中通知用户。如果该地址没有被注册,应用程序会要求用户访问一个唯一的、无法猜测的URL继续注册过程。这样可防止攻击者枚举有效的用户名(除非他们碰巧已经攻破大量电子邮件账户)。

防止蛮力攻击

必须对验证功能执行的各种质询采取保护措施,防止攻击者企图使用自动工具响应这些质询。这包括登录机制、修改密码功能和恢复遗忘密码等功能中的质询。

使用无法预测的用户名,同时阻止用户名枚举,给完全盲目的蛮力攻击设置巨大障碍,并要求攻击者在实施攻击前已经通过某种方式发现一个或几个特殊的用户名。

一些对安全性要求极高的应用程序(如电子银行)在检测到少数几次(如3次)登录失败后应立即禁用该账户,并要求账户所有者采取各种非常规步骤重新激活该账户,如给呼叫中心拨打电话并回答一系列安全问题。这种策略的缺点在于:它允许攻击者通过重复禁用合法用户的账户向他们发动拒绝服务攻击,因而增加了提供账户恢复服务的成本。一种更加均衡的策略适用于非常注重安全的应用程序,即在检测到少数几次(如3次)登录失败后将该账户冻结一段时间(如30分钟)。这种策略可有效阻止密码猜测攻击,同时可降低拒绝服务攻击风险,减轻呼叫中心的工作负担。

如果采用临时冻结账户的策略,应采取措施确保这种策略的效率。

 为防止信息泄露导致用户名枚举,应用程序绝不能透露任何账户冻结信息。相反,应用程序应对一系列即使是使用无效用户名发起的失败登录做出响应,通过一条常规消息提出警告:如果出现多次登录失败,账户将被冻结,建议用户稍后再试(如前文所述)。

 应用程序不应向用户透露账户锁定标准。只要告诉合法用户“稍后再试”并不会显著降低服务质量。但告知攻击者应用程序到底能够容忍多少次失败的登录尝试、账户冻结期有多长,就会让他们对任何登录尝试进行调整,不顾账户锁定策略而继续猜测密码。

 如果一个账户被冻结,那么应用程序不用检查用户证书,直接就可以拒绝该账户的登录尝试。因为一些应用程序在冻结期继续完全处理登录尝试,并且在提交有效证书时返回一条差异并不明显(或者差异比较明显)的消息,因此尽管应用程序执行账户冻结策略,攻击者仍然能够利用这种行为实施彻底有效的蛮力攻击。

账户锁定之类的常规应对措施对防御一种极其有效的蛮力攻击并没有帮助,即遍历大量枚举出的用户名,检查单独一个脆弱密码,如password。例如,如果5次登录失败就会触发账户冻结,这意味着攻击者能够对每个账户尝试使用4个不同的密码,而不会引起任何中断。如果一个应用程序使用许多脆弱密码,使用上述攻击手段的攻击者就能够攻破许多账户。

当然,如果验证机制其他区域的设计安全可靠,这种攻击的效率就会显著降低。如果攻击者无法枚举或有效预测出用户名,他就需要实施蛮力攻击以猜测用户名,其攻击速度也随之减慢。如果应用程序执行了严格的密码强度要求,攻击者更没有可能选择某个应用程序用户已经选择的密码进行测试。

除以上控制外,应用程序还可以在每个可能成为蛮力攻击目标的页面(见图6-9)使用CAPTCHA[2](全自动区分人类和计算机的图灵测试)质询,专门防御这种攻击。实际上,这种措施可防止攻击者向任何应用程序页面自动提交数据,从而阻止其手动实施各种密码猜测攻击。实际上,人们已经对CAPTCHA技术进行了大量的研究。有些时候,针对这种技术的自动攻击已经能够取得相当的成效。此外,一些攻击者甚至发起了破解CAPTCHA的竞赛,利用不知情的公众人物作为标靶帮助攻击者实施攻击。但是,即使一类特殊的质询无法完全生效,它仍然可使大多数随意的攻击者停止攻击行动,转而寻找并不使用这种技术的应用程序。

图6-9 旨在阻止自动攻击的CAPTCHA控件

 提示 攻击者在攻击一个使用CPATCHA控件阻止自动攻击的应用程序时一定会仔细检查图像页面的HTML源代码。我们曾遇到过许多实例,其中谜题的答案以文字形式出现在图像标签的ALT属性或一个隐藏表单字段中,这使精明的攻击者不必解开谜题就可以解除应用程序执行的保护。

防止滥用密码修改功能

应用程序应始终执行密码修改功能,允许定期使用的密码到期终止(如有必要)并允许用户修改密码(不管他们出于任何原因希望修改密码)。作为一种关键的安全机制,我们必须精心设计这项功能以防止滥用。

只能从已通过验证的会话中访问该功能。

不应以任何方式直接提供用户名,也不能通过隐藏表单字段或cookie提供用户名。用户企图修改他人密码的行为属非法行为。

作为一项高级防御措施,应用程序应对密码修改功能加以保护,防止攻击者通过其他安全缺陷,如会话劫持漏洞、跨站点脚本,甚至是无人看管的终端获得未授权访问。为达到这种目的,应要求用户重新输入现有密码。

为防止错误,新密码应输入两次。应用程序应首先比较“新密码”与“确认新密码”字段,看它们是否匹配,如果不相匹配,返回一条详细的错误消息。

该功能应阻止可能针对主要登录机制的各种攻击:应使用一条常规错误消息告知用户现有证书中出现的任何错误;如果修改密码的尝试出现少数几次失败,应临时冻结该功能。

应使用非常规方式(如通过电子邮件)通知用户其密码已被修改,但通知消息中不得包含用户的旧证书或新证书。

防止滥用账户恢复功能

当用户遗忘密码时,许多安全性至关重要的应用程序(如电子银行)通过非常规方式完成账户恢复:用户必须给呼叫中心打电话并回答一系列安全问题;新证书或重新激活代码也以非常规方式(通过传统的邮件)送往用户注册的家庭住址。绝大多数应用程序并不需要这种程度的安全保护,只需使用自动恢复功能即可。

精心设计的密码恢复机制需要防止账户被未授权方攻破,避免给合法用户造成任何使用中断。

绝对不要使用密码“暗示”之类的特性,因为攻击者可利用明显的暗示向账户发动攻击。

通过电子邮件给用户发送一个唯一的、具有时间限制的、无法猜测的一次性恢复URL是帮助用户重新控制账户的最佳自动化解决方案。这封电子邮件应送至用户在注册阶段提供的地址中。用户访问该URL即可设置新密码。之后,应用程序会向用户送出另一封电子邮件,说明密码已被修改。为防止攻击者通过不断请求密码重新激活电子邮件而向用户发动拒绝服务攻击,在证书得到修改前,用户原有证书应保持有效。

为进一步防止未授权访问,应用程序可能会向用户提出一个次要质询,用户必须在使用密码重设功能前完成该质询。设计质询时应小心谨慎,确保不会引入新的漏洞。

 应用程序应在注册阶段规定:质询应对每一名用户提出同一个或同一组问题。如果用户提供自己的质询,可能其中会有一些非常易于受到攻击,这也使攻击者能够通过确定那些自行设定质询的用户枚举出有效的账户。

 质询响应必须具有足够的随机性,确保攻击者无法轻易猜测出来。例如,询问用户就读的小学名称就优于询问他们最喜欢的颜色。

 为防止蛮力攻击,如果多次尝试完成质询都以失败告终,应临时冻结相关账户。

 如果质询没有得到正确响应,应用程序不应泄露任何相关信息,如用户名的有效性、账户冻结等。

 成功完成质询后,应继续完成上文描述的处理过程,即向用户注册的电子邮件地址发送一封包含重新激活URL的电子邮件。无论在什么情况下,应用程序都不得透露用户遗忘的密码或简单将用户放入一个通过验证的会话中。此外,最好不要直接进入密码重设功能,因为与初始密码相比,攻击者通常更容易猜测出账户恢复质询的响应,因此应用程序不应依赖它对用户进行验证。

日志、监控与通知

应用程序应在日志中记录所有与验证有关的事件,包括登录、退出、密码修改、密码重设、账户冻结与账户恢复。应在适当的地方记录所有失败与成功的登录尝试。日志中应包含一切相关细节(如用户名和IP地址),但不得泄露任何安全机密(如密码)。应用程序应为日志提供强有力的保护以防止未授权访问,因为它们是信息泄露的主要源头。

应用程序的实时警报与入侵防御功能应对验证过程中的异常事件进行处理。例如,该功能应向应用程序管理员通报所有蛮力攻击模式,便于他们采取适当的防御与攻击措施。

应以非常规方式向用户通报任何重大的安全事件。例如,用户修改密码后,应用程序应向他注册的电子邮件地址发送一封邮件。

应以非常规方式向用户通报经常发生的安全事件。例如,用户成功登录后,应用程序应向用户通报上次登录的时间与来源IP/域,以及从那以后进行的无效登录尝试的次数。如果用户获悉其账户正遭受密码猜测攻击,他就更有可能会经常修改密码,并设置一个安全性高的密码。

小结

验证功能可能是应用程序受攻击面中的首要目标。并无特权的匿名用户可直接访问该功能;如果攻击者破坏了该功能,就可以访问受到保护的功能和敏感数据。验证功能是应用程序安全防御机制的核心,也是防御未授权访问的前沿阵地。

现实中的验证机制存在着大量的设计与执行缺陷。使用系统化的方法尝试各种攻击途径,即可对这些缺陷发起全面有效的攻击。许多时候,攻击目标显而易见,如保密性不强的密码、发现用户名的方法和蛮力攻击漏洞。另一方面,有些缺陷隐藏得很深,需要对复杂的登录过程进行仔细的分析才能发现可供利用以“敲开应用程序大门”的细微逻辑缺陷。

“四处查探”是攻击验证功能最常用的方法。除主登录表单外,可能还包括注册新账户、修改密码、记住密码、恢复遗忘的密码与伪装其他用户等功能。以上每一种功能都可能成为潜在缺陷的主要来源,在一项功能中特意避免的问题往往又会在其他功能中重新出现。花费一些时间仔细检查所能发现的每一个受攻击面,应用程序验证机制的安全性将会得到显著增强。

问题

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

(1)在测试一个使用joe和pass证书登录的Web应用程序的过程中,在登录阶段,在拦截代理服务器上看到一个要求访问以下URL的请求:

http://www.wahh-app.com/app?action=login&uname=joe&password=pass

如果不再进行其他探测,可以确定哪3种漏洞?

(2)自我注册功能如何会引入用户名枚举漏洞?如何防止这些漏洞?

(3)一个登录机制由以下步骤组成:

(a)应用程序要求用户提交用户名和密码;

(b)应用程序要求用户提交值得纪念的词中的两个随机选择的字母。

应用程序为何要求用户分两个阶段提供所需的信息?如果不这样做,登录机制将存在什么缺陷?

(4)一个多阶段登录机制要求用户首先提交用户名,然后在后续阶段中提交其他信息。如果用户提交任何无效的数据,立即返回到第一个阶段。

这种机制存在什么缺点?如何修复这种漏洞?

(5)应用程序在登录功能中整合了反钓鱼机制。注册过程中,每名用户从应用程序提供的大量图片中选择一幅特殊的图片。登录机制由以下步骤组成:

(a)用户输入其用户名和出生日期;

(b)如果这些信息无误,应用程序向用户显示他们选择的图片,如果信息有误,随机显示一幅图片;

(c)用户核实应用程序显示的图片,如果图片正确,输入他们的密码。

反钓鱼机制的作用在于:它向用户确认,他们使用的是真实而非“克隆”的应用程序,因为只有真正的应用程序才能显示正确的图片。

反钓鱼机制给登录功能造成什么漏洞?这种机制能够有效阻止钓鱼攻击吗?

注释

[1] CAPTCHA项目是 Completely Automated Public Turing Test to Tell Computers and Humans Apart (全自动区分计算机和人类的图灵测试)的简称,已由卡内基梅隆大学注册商标。CAPTCHA是区分计算机和人类的一种程序算法,这种程序必须能生成并评价人类能很容易通过但计算机却通不过的测试。这个要求本身就是悖论,因为这意味着一个CAPTCHA必须能生成一个它自己不能通过的测试。——译者注

[2] 社会工程(Social Engineering)是一种利用人的弱点(如人的本能反应、好奇心、信任、贪婪等进行诸如欺骗、伤害等危害手段),获取自身利益的攻击方法。——译者注

浙ICP备11005866号-12