在OWASP Top 10中,有一类漏洞的大类,称之为越权访问(Broken Access Control, 简称BAC)。顾名思义,这类漏洞是指应用在检查授权(Authorization)时存在纰漏,使得攻击者可以利用一些方式绕过权限检查,访问或者操作到原本无权访问的代码。在实际的代码安全审查中,这类漏洞往往很难通过工具进行自动化检测,因此在实际应用中危害很大。经验证明,解决这类问题,往往需要从系统设计上着手。
猛买网张磊:基于又拍云存储解决静态文件存储问题
我们以一个实际存在过的BAC漏洞为例,来看看这种问题发生的根源及解决办法是什么。
国内互联网漏洞报告平台乌云在去年年底报告了一个存在于国内某知名电子商务网站的BAC漏洞wooyun-2011-03271(http://www.wooyun.org/bugs/wooyun-2011-03271)。该网站存在一个公开的HTTP接口myaddress.aspx。此接口根据接收的参数addressid来返回json格式的消费者收货地址数据,然后在前端页面上由JS解析进行展示。这是一个很多同类网站都会用到的一个普通功能。
在乌云的漏洞报告中,我们看到,白帽子利用传入参数的可猜测性(使用整形数据做记录id),直接变更输入的addressid参数,就可以拿到他人隐私信息。这是一个典型的BAC问题。修复也非常简单,只要在服务端对请求的数据和当前用户身份做一个校验检查即可。但是在这类应用中,诸如此类的数据如此之多,从地址数据,到订单信息、支付信息等等,无一不需要小心处理。当业务复杂到一定程度之后,没有谁能够保证这些数据的访问都经过了严格的权限检查。所以出现BAC问题,似乎是难以避免的。
让我们从设计的角度重温一下这个用例中的一些普遍特征。
对于收货地址而言,在这类B2C商务平台上应该存在几类不同的访问场景:
对于平台运营商来说,需要有权限对这些数据进行读取操作。
对于在平台上开展销售的商家来说,一旦与该用户产生了直接的交易,那么也应该对这些数据有权进行操作(实际上地址数据会在产生订单时发生克隆,我们简化这个过程,视之为引用关系)。
对于消费者本身,应该有权对这些数据进行操作。
那么最基本的,针对这些场景,我们需要对目标数据的访问进行不同逻辑的权限检查。
如果是平台运营系统请求数据,那么不需要进行权限校验。
如果是平台商家用户请求数据,那么应该检查该数据的拥有者,即消费者,是否授权该商家访问其地址信息(判断依据为检查是否产生交易) 。
对于消费者本身,需要检查目标数据的拥有者与请求方是否同一用户。
这三类场景需要的检查逻辑各不相同,那么在后端,我们需要什么样的API或者服务(Service)来为前端的这三种场景提供数据呢?
通常的系统中都会存在类似GetAddressById这样的API或者服务,来为前端系统服务。那么接下来前端系统需要根据自身的场景,来实现各自的校验逻辑。但是,这样合理吗?由于这些API的粒度是如此之小,任何一个开发人员,在实现任何一个业务场景时都可能会使用到。这些使用到的地方,如何保证都经过严格的权限检查呢?
可能AOP是一种解决问题的方式,但是实际实现中,你会发现,很难针对不同的使用场景而提供一致的权限校验方式。姑且不论性能高低,仅就用户身份的确认这一点,就很难在统一的代码中去适应不同的场景。
那么我们就需要重新来审视GetAddressById这种API了。对于平台运营商系统的使用场景,这个API没有任何问题。但是对于平台商家后台和消费者系统,是否需要类似于GetAddressByIdAndMerchantId和GetMyAddressById这种API呢?
仅仅增加了不同粒度的后端API,这并不能保证从此可以高枕无忧了。对前端开发者来说,如果这3个API都可见可用,那么很难保证不会被使用在不恰当的场景中。
一个在实际应用中被采用的解决方案是,把这3个API封装到不同的服务(Service)或者包(Package)中,并在不同的项目中设置仅对自己的场景可见。这样在每个应用中,也只能使用符合自己的业务场景安全要求的后端API/Service。
现实中的情况可能复杂很多,贯穿业务系统前后端的ACL(访问控制列表)在实现上会非常繁琐而且成本较高,也可能并不适用于每个应用场景。但是分清楚数据的安全域划分,根据不同的场景设计符合要求的后端API/Service,同时对后端API/Service根据不同的安全域进行隔离,这是非常实用的一种思路。
好的后端API/Service设计,会让开发者即使并不清楚相关的安全考量和安全开发知识,依然能够开发出符合授权标准的应用。这也是安全架构的重要组成部分。