访问控制可分为三大类:垂直访问控制、水平访问控制和上下文相关的访问控制。
垂直访问控制允许各种类型的用户访问应用程序的不同功能。在最简单的情况下,应用程序通过这种控制界定普通用户和管理员。在更加复杂的情况下,垂直访问控制可能需要界定允许其访问特殊功能的各种不同类型的用户,给每个用户分配一个单独的角色,或一组不同的角色。
水平访问控制允许用户访问一组相同类型的、内容极其广泛的资源。例如,Web邮件应用程序允许访问自己而非他人的电子邮件;电子银行只允许转移自己账户内的资金;工作流程应用程序允许更新分配给你的任务,但只能阅读分配给他人的任务。
上下文相关的访问控制可确保基于应用程序当前的状态,将用户访问仅限于所允许的内容。 例如,如果在某个过程中,用户需要完成多个阶段的操作,上下文相关的访问控制可以防止用户不按规定的顺序访问这些阶段。
许多时候,垂直与水平访问控制相互交叠。例如,企业资源规划应用程序允许每个应付账会计文员支付某一个组织单元、而非其他单元的发票,但允许应付账经理支付任何单元的发票。同样,会计文员只能支付小额发票,而大额支票必须由经理支付。财务总监可以查看公司每个组织单元的发票支付和收据,但不得支付任何发票。
如果用户能够访问他无权访问的功能或资源,就表示访问控制存在缺陷。主要有三种类型的以访问控制为目标的攻击,分别与三种访问控制相对应。
如果一名用户能够执行某项功能,但分配给他的角色并不具有这种权限,就表示出现垂直权限提升
漏洞。例如,如果一名普通用户能够执行管理功能,或者一位会计文员能够支付任何金额的发票,就表示访问控制并不完善。
如果一名用户能够查看或修改他没有资格查看或修改的资源,就表示出现水平权限提升
漏洞。例如,如果用户能使用Web邮件应用程序阅读他人的电子邮件,或者如果一位会计文员可以处理自己所属组织单元以外的单元的发票,那么访问控制也不完善。
如果用户可以利用应用程序状态机中的漏洞获得关键资源的访问权限,就表示出现业务逻辑漏洞
。例如,用户能够避开购物结算序列中的支付步骤。
许多时候,应用程序水平权限划分中存在的漏洞可能会立即引起垂直权限提升攻击。例如,如果一名用户能够以某种方式设置其他用户的密码,那么该用户就能攻击管理员的账户并控制整个应用程序。
在我们已经描述的示例中,不完善的访问控制使获得某种用户权限的攻击者能够执行未授权操作或访问未授权数据。但是,在最严重的情况下,不完善的访问控制可能允许完全未获授权的用户访问只有特权用户才能访问的功能或数据。
在许多的访问控制不完善情况下,敏感功能和数据可被任何知道相关URL的用户访问。例如,在许多应用程序中,任何人只需访问一个特定的URL就能够完全控制它的管理功能:
在这种情况下,应用程序通常仅实施如下访问控制:以管理员身份登录的用户在他们的用户界面上看到一个该URL的链接,而其他用户则无法看到这个链接。这种细微的差别是应用程序用于“防止”敏感功能被未授权使用的唯一机制。
有时候,允许用户访问强大功能的URL可能很难猜测,甚至可能相当隐秘,例如:
这种情况下,开发者假设攻击者无法知道或发现这个URL,管理功能就会因此受到保护。当然,局外人很难攻破一个应用程序,因为他们不太可能猜测出实现这种目的的URL。
错误观点
“低权限用户并不知道那个URL。我们并没有在应用程序中引用它。”
在前面的示例中,无论URL多么容易猜测,不存在任何真正的访问控制仍然等同于一个严重的漏洞。不管是在应用程序还是在用户手中,URL都不具有保密性。它们显示在屏幕上,出现在浏览器历史记录与Web服务器和代理服务器的日志中。用户可能会记下它们,以它们为书签或通过电子邮件将其四处传播。与密码不同,它们一般不需要定期修改。当用户的工作职位发生改变、需要收回他们的管理权限时,我们并没有办法从他们的记忆中删除某个特殊的URL。
一些应用程序的敏感功能隐藏在各种不太容易猜测的URL之后,但攻击者通过仔细检查客户端代码仍能发现这些URL。许多应用程序使用JavaScript在客户端动态建立用户界面。它一般建立各种与用户状态有关的标记,然后根据这些标记在用户界面(UI)中增加不同的元素。例如:
在这个示例中,攻击者只需检查JavaScript代码就可确定具备管理功能的URL,并尝试访问它们。在其他情况下,HTML注释中可能包含屏幕显示内容中没有链接的URL的引用或线索。请参阅第4章了解攻击者收集应用程序中隐藏内容信息时使用的各种技巧。
直接访问方法
如果应用程序披露实际用于远程调用API方法的URL或参数(通常它们由Java界面披露),这时可能出现功能不受保护的特例。在将服务器端代码移至浏览器扩展组件,并创建方法存根以便代码仍然能够调用它正常运行所需的服务器端方法时,往往会发生这种特例。除以上情形外,如果URL或参数使用getBalance和isExpired等标准Java命名约定,这时也可能会出现直接调用方法的情况。
原则上,与指定服务器端脚本或其他资源的请求相比,并不需要完全确保指定要执行的服务器端API的请求的安全。但实际上,这种机制往往包含漏洞。通常,客户端直接与服务器端API方法交互,并避开应用程序的正常访问控制或意外输入向量。如果其他功能从不由Web应用程序客户端直接调用,则这些功能也可以通过上述方法调用,并不受任何控制的保护。一般情况下,用户只需要能够访问某些特定的方法,但他们却拥有访问所有方法的权限。出现这种情况,或者是因为开发者并不了解用户到底需要哪些方法,因而向他们提供所有方法的访问权限;或者是因为将用户映射到HTTP服务器的API默认提供访问所有方法的权限。
以下示例显示了如何从接口securityCheck中调用getCurrentUserRoles方法:
在此示例中,除了测试对getCurrentUserRoles方法的访问控制外,还应检查是否存在其他类似命名的方法,如getAllUserRoles、getAllRoles、getAllUsers和getCurrentUserPermissions。我们将在本章后面部分进一步讨论如何测试直接访问方法的情况。
当应用程序使用一项功能访问某个特殊的资源时,被请求资源的标识符常常以请求参数的形式、在URL查询字符串或POST请求主体中提交给服务器。例如,应用程序可能使用下面的URL显示一份属于某个用户的特殊文档:
拥有这份文档的用户登录后,这个URL的链接将会在该用户的“我的文档”页面显示。其他用户无法看到这个链接。但是,如果访问控制不完善,那么请求相应URL的任何用户都能够像授权用户那样查看这份文档。
提示
当主应用程序连接一个外部系统或后端组件时通常会出现这类漏洞。可能很难在使用各种技术的不同系统之间共享一个基于会话的安全模型。面对这种问题,开发者往往会避免使用那种模型,而使用客户端提交的参数做出访问控制决定。
在这个示例中,寻求获得未授权访问的攻击者不仅需要知道应用程序页面的名称(View-Document.php),而且需要知道他想要查看的文档的标识符。有时,应用程序生成的资源标识符非常难以预测,例如,它们可能是随机选择的GUID(Global Unique Identifier,全局统一标识符)。在其他情况下,它们可能很容易猜测,例如,它们可能是连续生成的数字。但是,无论是哪一种情况,应用程序都易于遭受攻击。如前所述,URL并不具有保密性,资源标识符也同样如此。通常,希望发现其他用户资源标识符的攻击者可在应用程序的某个位置找到这些信息,例如访问日志中。即使在应用程序的资源标识符很难猜测的情况下,如果没有对那些资源实施合理的访问控制,它们仍然易于受到攻击。如果标识符很容易预测,问题就会更加严重,也更容易被攻击者所利用。
提示
应用程序日志通常是一个信息金矿,其中包含大量可被用作标识符的数据项,可利用它们探查通过标识符访问的功能。应用程序日志中常见的标识符包括:用户名、用户ID、账号、文档ID、用户群组与角色以及电子邮件地址。
注解
除用于指代应用程序中基于数据的资源外,这种标识符还常用于表示应用程序功能。如第4章所述,应用程序可以通过单独一个页面提供各种功能,它接受一个功能名称或标识符为参数。同样,在这种情况下,应用程序也只是在各种类型的用户界面中显示或隐藏一个特殊的URL,实施肤浅的访问控制。如果攻击者能够确定某一敏感功能的标识符,他就能像拥有高级权限的用户一样访问该功能。
应用程序的许多功能通过几个阶段执行,并在执行过程中由客户端向服务器发送许多请求。例如,添加新用户功能可能包括从用户维护菜单中选取这个选项,从下拉列表中选择部门和用户职位,然后输入新用户名、初始密码和其他信息。
许多应用程序常常会努力防止这种敏感功能被未授权访问,但由于其误解了这种功能的使用方式,因而实施了不完善的访问控制。
在前面的示例中,如果一名用户试图加载用户维护菜单并从中选取“添加新用户”选项,应用程序就会核实该用户是否拥有必要的权限,如果用户未获授权,就阻止其进行访问。但是,如果攻击者直接进入核实用户所属部门和其他细节的阶段,可能就没有有效的访问控制对其加以限制。开发者认为,任何到达验证过程后续阶段的用户一定已经拥有相关的权限,因为前面的阶段已经验证了这些权限。通过这种方法,任何应用程序用户都能够添加一个新的管理用户账户,因而完全控制整个应用程序,访问许多其他已经实施完善的访问控制的功能。
即使在许多电子银行使用的安全性能很关键的Web应用程序中,我们也曾经发现这种类型的漏洞。在银行应用程序中,转账通常包括几个阶段,部分原因是为了防止用户在请求转账时无意出错。这个多阶段过程需要在每个阶段收集各种与用户有关的数据。这些数据在初次提交后将接受严格检查,然后使用HTML表单字段送交给随后的阶段。但是,如果应用程序并不在最后阶段重新确认这些数据,攻击者就可能会避开服务器检查。例如,应用程序可能会核实进行转账的来源账户是否属于当前用户,然后询问与目的账户有关的细节和转账的金额。如果用户拦截这个过程中的最后一个POST请求,并修改来源账号,他就能实现水平权限提升,从其他用户的账户中转移资金。
在绝大多数情况下,用户都是通过向在服务器上执行的动态页面发布请求来访问受保护的功能和资源。这时,每个动态页面负责执行适当的访问控制检查,并确认用户拥有执行相关操作所需的权限。
但是,有些时候,用户会直接向位于服务器Web根目录下的静态资源提出请求,要求访问这些受保护的资源。例如,一个在线出版商允许用户浏览他的书籍目录并购买电子书进行下载。支付费用后,应用程序就将用户指向以下下载URL:
因为这是一个完全静态的资源,所以它并不在服务器上运行,它的内容直接由Web服务器返回。因此,资源自身并不能执行任何检查以确认提出请求的用户拥有必要的权限。如果可以通过这种方式访问静态资源,那么这些资源很可能没有受到有效的访问控制机制的保护,任何知晓URL命名方案的人都可以利用这种缺陷访问任何所需的资源。在上面的示例中,文档名称似乎是一个ISBN,利用这个信息,攻击者能够任意下载由该出版商制作的每一本电子书。
某些功能特别容易出现这种问题,包括提供公司年度报表之类静态文档的金融Web站点、提供可下载二进制代码的软件供应商以及通过其访问应用程序中静态日志文件和其他敏感数据的 管理功能。
一些应用程序在Web服务器或应用程序平台层使用控件来控制访问。通常,应用程序会根据用户的角色来限制对特定URL路径的访问。例如,如果用户不属于“管理员”组,访问/admin路径的请求可能会遭到拒绝。原则上,这是完全合法的访问控制方法。但是,如果在配置平台级控件时出现错误,这时就可能导致未授权访问。
正常情况下,平台级配置与防火墙策略规则类似,它们基于以下条件允许或拒绝访问请求:
HTTP请求方法;
URL路径;
用户角色。
如第3章所述,GET方法的最初目的是检索信息,而POST方法的目的是执行更改应用程序的数据或状态的操作。
如果没有小心制定规则,以基于正确的HTTP方法和URL路径允许访问,就可能会导致未授权访问。例如,如果用于创建新用户的管理功能使用POST方法,平台可能具有禁止POST方法并允许所有其他方法的拒绝规则。但是,如果应用程序级脚本并不验证针对此功能的所有请求是否使用POST方法,则攻击者就可以通过使用GET方法提交同一请求来避开这种控制。由于大多数用于检索请求参数的应用程序级API对于请求方法并无限制,因此,攻击者只需要在GET请求的URL查询字符串中提供所需参数,就可以未授权使用上述功能。
令人更加惊奇的是,即使平台级规则拒绝访问GET和POST方法,应用程序仍有可能易于受到攻击。这是因为,使用其他HTTP方法的请求可能最终由处理GET和POST请求的相同应用程序代码来处理。HEAD方法就是一个典型的例子。根据规范,服务器应使用它们用于响应对应的GET请求的相同消息头(但不包含消息主体)来响应HEAD请求。因此,大多数平台都能够正确处理HEAD请求,即执行对应的GET处理程序并返回生成的HTTP消息头。通常,GET请求可用于执行敏感操作,这或者是因为应用程序本身将GET请求用于这一目的(与规范不符),或者是因为它并不验证是否使用了POST方法。如果攻击者能够使用HEAD请求增加一个管理用户账户,那么,即使在请求中未收到任何消息主体,他仍然能够成功实施攻击。
某些情况下,对于使用无法识别的HTTP方法的请求,平台会直接将它们交由GET请求处理程序处理。在这种情况下,通过在请求中指定任意无效的HTTP方法,就可以避开拒绝某些指定的HTTP方法的平台级控制。
我们将在第18章中讨论Web应用程序平台产品中包含此类漏洞的一个特例。
一些应用程序使用一种极其不安全的访问控制模型,基于客户端提交的请求参数或受攻击者控制的其他条件做出访问控制决定。
在一些这种模型中,应用程序在用户登录时决定用户的角色或访问级别,并在登录后通过隐藏表单字段、cookie或者预先设定的查询字符串参数(参阅第5章了解相关内容)由客户端传送这些信息。应用程序在处理随后请求的过程中读取这个请求参数,并为用户分配相应的访问级别。
例如,使用应用程序的管理员将看到以下URL:
但普通用户看到的URL中包含一个不同的参数,或者根本不包含任何参数。任何知道分配给管理员的参数的用户只需在他自己的请求中使用这个参数,就可以访问管理功能。
有时候,如果不以高级权限用户的身份实际使用应用程序,并确定在使用过程中提出了哪些请求,这种类型的访问控制可能很难探测出来。作为普通用户,我们可以使用在第 4章讨论的如何发现隐藏请求参数的技巧成功查明这种机制。
2.基于Referer的访问控制
在其他不安全的访问控制模型中,应用程序使用HTTP Referer消息头做出访问控制决定。例如,应用程序可能会根据用户的权限,严格控制用户访问主维护菜单。但是,如果某名用户提出请求,要求访问某项管理功能,应用程序可能只是检查该请求是否由管理菜单页面提出,如果确实由该页面提出,即认为该用户一定已经访问过那个页面,并因此已经拥有了必要的权限。当然,从本质上讲,这种模型并不安全,因为Referer消息头完全由用户控制,并可设定为任何值。
3.基于位置的访问控制
许多公司都具有管理或业务要求,根据用户的地理位置限制对资源的访问。这些公司不仅包括金融机构,还包括新闻服务及其他部门。在这些情况下,公司会采用各种方法来确定用户的位置,其中最常用的是用户当前IP地址的地理位置。
攻击者能够轻易突破基于位置的访问控制。以下是一些常用的方法:
使用位于所需位置的Web代理服务器;
使用在所需位置终止的VPN;
使用支持数据漫游的移动设备;
直接修改客户端用于确定地理位置的机制。