一、背景介绍
在网上,大家经常可以看到诸如数据库被拖库、用户信息泄露等因为安全漏洞引发的问题,给用户和公司都造成了较大的损失。随着公司业务快速发展、功能增多、用户数目不断增加,安全问题越来越成为一个必须重视的问题。
在实践安全测试的过程中,业务部门和安全部门合作去进行安全测试,早期测试人员和安全同学通过手工执行安全测试用例来发现问题,后续慢慢的也开始使用一些安全工具,通过自动化的方式来提高发现问题的效率。在这过程中,我们关注到了一个跟业务密切相关的安全问题——接口越权问题,并设法通过自动化扫描的方式发现该类问题,提高效率。
越权问题是指应用对访问请求的权限检查存在纰漏,使得攻击者在使用没有获得权限的用户账户后,利用一些方式绕过权限检查,访问或者操作其他用户或者更高权限者的对象。比如商家 A 能够查看到商家 B 的营业数据(水平越权),店铺 C 的客服人员能够像该店店长那样进行采购(垂直越权)。越权漏洞的成因主要是因为开发人员在对数据进行增、删、改、查询时,没有对发起请求的人是否有权限进行验证。
为什么会选择这个类型的安全问题作为关注对象,原因有三:
一、本人所在业务线对外暴露的网关接口有2000+,手工测试成本较高,希望通过一些自动化的方式提高效率。
二、目前市面上的开源工具对于如 SQL 注入、跨站脚本、端口暴露等通用的安全问题有了较为成熟的解法,但是很少能够对越权问题提出有效的解决方案。究其原因,账户权限体系是怎样的、请求访问的对象是不是私有的、结果返回是否带有越权的信息 这三个关键的因素是和业务强相关的,通用的方法难以回答这三个问题。而凭借着对业务知识的了解,业务线研发人员也许能够针对性的回答这三个问题,从而提出一种发现越权问题的自动化解决方案。
三、越早发现安全问题并进行修复,修复和回归的成本就越低,所以希望在早期研发阶段就能发现问题。
二、基本模型
大部分自动化测试工具都是对于手工测试的归纳总结,这里先让我们先梳理一下手工执行越权测试的方式。
如果手工来测试接口是否越权,那么可能的步骤如下:
- 使用一个正常的账号在页面上操作请求,比如登陆店铺A的员工账号,查询订单1
- 获取到了查询订单1的请求接口名称、参数以及返回
- 换一个账号,比如登陆店铺B的员工账号,调用下同一请求,对订单1再次进行查询(也可以直接用工具篡改原始请求的 cookie 替换为店铺 B 的再次进行请求,原理是一样的)
- 然后观察下此时返回的信息是否越权。如果店铺 B 的员工也获取到了订单1的信息,那么就存在越权的问题;如果报错没有权限,那么说明系统有针对性的做了权限检查。
根据上述过程归纳一下,要进行越权检查所需的条件是:
- 获取到请求
- 准备多个账号
- 结果判断
据此抽象出一个模型,按照这个模型进行后续的设计:
这个模型分为三个部分,第一部分是通过业务对外访问的入口获取到请求,用于执行越权的检查;第二部分是将请求组装,分别让有权限的账号和无权限的账号进行调用,获取对应的返回;第三部分也是最核心的部分,如何识别是否越权,返回对应的检查结果。
三、整体方案设计
根据上一节整理出的检查模型,整体方案也是按照三部分进行设计,如图所示:
在流量获取方面,首先考虑的就是环境问题:在哪个环境获取到请求,再去哪个环境回放。因为执行写接口的水平越权访问可能会造成脏数据,所以整个扫描的环境选择在测试环境。选择测试环境另一个好处是可以提前发现在测试中的、还没上线的接口存在的安全问题,直接在项目测试的过程进行修复和回归验证,这样修复成本和推动难度都是最小的。
下一步,把获取到的请求通过采样和筛选后,重新组装进行重放。重放时至少需要准备一个没有权限的账号,和一个有权限的账号。对于零售业务场景来说,水平越权主要针对的是跨店铺的水平越权。比如有两家店铺 A 和 B ,每家店铺都有店员和高管角色,如果店铺 A 的高管能访问店铺 B 高管所能访问的敏感信息,那就存在水平越权,如果店铺 A 的店员能操作只有店铺 A 高管才能操作的功能,那就存在垂直越权。所以,对于垂直越权来说,准备同一家店铺不同角色的账号来模拟有权限和没有权限访问的情况。而对于水平越权来说,有权限的账号可以采用原始请求里的账号信息,而没有权限的账号可以单独准备一个新店铺的账号。
最后,获取到对应返回,根据对应的检查类型采用不同的方式进行检查。垂直越权主要看下接口是否配置了权限,权限检查是否生效。水平越权复杂一些,需要根据接口参数中访问的对象、接口操作的行为等多个特征进行一个综合判断。
四、核心算法设计
在接口越权扫描平台当中,能够发现越权问题立足于一条最基本逻辑,那就是:有权限的人获取到的对象信息,无权限的人获取不到,从两者的返回上能感知到这种区别。
围绕这条基本判断标准,主要的算法逻辑都是为了提高越权问题的发现效率以及减少误报做努力的。要做的就是要让代码尽可能识别出访问的对象,同时能够理解返回的对象信息,识别出越权的情况。
如何达成这个目标,是很有难度和挑战的:
- 从请求中能够获取到的信息很少,但是返回的结果又是各式各样的,判断是否越权,往往需要借助一些额外信息
- 对于越权的处理没有标准化,尤其在水平越权中这个现象比较严重:一些情况下代码对越权是做了单独校验,另一些是通过关联查询数据来做防御,在返回的表现上往往不一样。很多额外的工作量就是在识别各种没有标准化的返回上。
所以这一块需要进行不断的观察分析和优化。
4.1 智能采样
要提高发现问题的效率,需要用尽可能少的请求发现尽可能多的问题。所以获取请求时并不把所有请求都原样进行处理,而是采用批量获取,然后按照接口名分组,对于每个请求按照接口返回的内容做优先级排序,基本的优先级规则是:正常返回>异常的返回;信息返回量大>信息返回量小。形成如图所示的列表:
最后按照设置的采样值,以接口维度按优先级选取需要回放的请求。通过这种方式尽可能让有信息量、质量较高的请求进入回放过程,避免一些空值返回或者异常返回导致越权检查无法判断,减少人工介入排查的成本。
4.2 请求参数的越权风险判断
在预检查过程中,会对水平越权的参数检查是否存在安全风险进行检查。参数是一个很重要的安全特征,我们需要知道这个请求访问的是个什么样的对象,才能帮助我们更好的判断这个接口是否有安全问题。一个请求里面有各种各样的参数,但是我们真正关注的是有注入风险的参数,比如用户 id、订单号、店铺 id 等。
我们定义了一个简单的参数模型。将参数分为系统参数、公共对象、业务对象。如果请求的入参中不含对象信息,比如只传了 pageno、pagesize 这些系统参数进行列表查询,那么请求的对象默认就是自己的账号,没有什么风险;如果请求参数是一些对象参数,比如订单号和上传模版,那需要区分是否存在风险,比如上传模版是所有店铺都能获取到的、统一的公共信息,那么谁都可以访问查询不构成越权;而订单号对应着订单信息,是店铺私有的数据,那么就不能别的店铺都获取到。目前对于公共信息进行了白名单配置,不在白名单内的对象参数都默认为是业务对象,如果存在至少一个业务对象,就需要进一步进行检查。
4.3 垂直越权检查
垂直越权的检查相对简单,不需要关注返回的业务数据的具体内容,而是关注返回的 code,因为权限检查要先于业务逻辑处理,不管业务逻辑是否能正常处理,如果无权限应当返回权限校验失败的错误code。如果接口在权限平台进行了配置,那么查询权限平台会告诉是否允许操作,如果无权限的账号对该接口的访问操作,查询权限平台的结果是无权限的,说明该接口至少已经做过权限配置,剩下的需要检查下实际配置是否生效、是否返回了无权限的错误 code。实际扫描过程中,曾经发现不少接口在权限平台进行了配置,但配置有误、实际上没有生效的乌龙情况。另外考虑到可能有一部分接口是自己做了权限判断,那么对有权限和无权限的账号的返回 code 做个比对,如果返回不一致说明权限检查生效。
一个检查的范例:
目前垂直越权检查重点在于0-1的检查,即接口的权限是否配置、配置是否生效,而具体的不同账号角色配置的权限是否符合业务要求,还是需要人工进行检查。
4.4 水平越权检查
水平越权的检查中,返回的结果多种多样,这对如何判断越权产生了较大的困难。目前的解决方法是,识别接口不同行为,然后执行不同策略来提高准确度。通过规则设置和算法优化,把接口越权判断的正确率,从单纯只比较返回一致性的70%,提高到了90%。
4.4.1 行为识别
行为识别的目的主要是识别接口是做什么操作的,每种操作可以用对应的算法进行判断。目前简单的分为读和写两种类型,对于每一种类型都有独立的检查算法。后面可以扩展为更细粒度的比如查询、更新、删除、导出等更细分的操作,提供更准确的检查方法。接口行为的识别目前是按照接口名称进行判断。
举个例子,一个接口名称叫做 youzan.retail.finance.settlement.item.list.1.0.0
,那么 item 往往标识了操作的对象,而 list 则标识了行为,所以从接口名可以大概推断出这个接口是做什么的。所以可以建立一个关键字的字典,通过字段的名字来匹配到属于哪一种行为。但是,有一部分接口并没有老老实实按这个规则进行命名,比如把行为在前,对象在后,或者行为的那个名字取的不是 query 而是 queryStatus 这样子的名字。所以在匹配的时候根据匹配的程度进行打分,全部匹配时分数较高,部分匹配时分数较低,根据接口名命中的字段和分数最后计算出属于读接口还是写接口。
举个例子:
读操作的关键字字典为 [“query”,“list”,“get”]
写操作的关键字字典为 [“create”,“delete”,“update”,“config”]
给定接口 youzan.retail.trademanager.get.selffetchpointconfig.1.0.0
这个接口可能会同时命中了 get 和 config两个关键字,get 是查询行为,能够在给定的查询操作的字典中找到全匹配的值,记2分;selffetchpointconfig 不在字典中,但能部分匹配到 config 这个关键字,config 存在于写操作的字典中,记1分,最后经过计算最后将这个接口的类型定义为读请求。
4.4.2 读请求水平越权检查
对读请求的水平越权判断,通用的算法即是比较两个请求的返回是否一致,如果一致则说明存在水平越权,大部分情况下都能按这个规则发现问题。
在此基础上还会存在很多特殊的情况,比如有权限的返回和越权的返回的对象都为空的列表或者默认值,导致两边返回的信息是一致的,这种情况下就无法判断是否越权了,就需要通过识别无意义的返回或者非敏感信息的返回,将其判断为不越权,直到有新的有意义的返回能够帮助判断这个接口的确是越权/不越权。所以针对性的增加了对返回值是否是空列表,或者都为默认值(0 [] ""等)的检查规则。
如以下返回都会被根据规则认为返回的是空信息或者非敏感信息,从而判断为没有越权风险:
"data":{"total":0,"page_no":1,"page_size":25}
"data":[]
"data":{"is_bind":false}
4.4.3 写请求水平越权检查
如果是写请求接口的水平越权判断,先比较两者请求的返回是否一致。如果两者请求都成功,且返回一致,则可能有越权风险。比如更新商品信息,也许会对于无权限的调用返回的是商品不存在或没有权限,也可能都返回了成功,但实际没有操作生效,只有实际查询该商品信息才能直到是否真正成功,这就是开头所讲没有标准化造成的问题。目前这种情况越权算法还无法很好的识别,故先标记为人工确认,需要流转给人工进行处理,后续再进行改进。
4.4.4 后置检查
一个系统需要有一定的反馈机制才能优化逻辑,提高逻辑判断的准确性,在后置检查中,通过沉淀之前识别出来的问题以及人工确认过的问题,来对需要人工确认的问题进行推断,目前对于需要人工确认的检查结果,会去查一下历史检查记录,如果近期已经确认过,则标记为通过。当接口的字段发生变化或者离上次确认的时间间隔较长,历史检查记录也会标记为待确认,需要定期再次确认。后面期望能够通过算法去挖掘和学习已经发现和确认的问题,能够更智能的给出判断结果。
五、整体平台设计
除了核心算法外,整个平台提供了运行管理、接口管理、结果管理等的能力,提供了越权配置、任务执行、结果展示和分析等功能,方便研发同学主动使用和管理安全问题。
如图,通过界面可以查看接口覆盖的情况,进行查看和操作。
六、实践和落地
有了接口越权扫描平台后,安全问题发现到解决的流程为:
- 平台每天汇总扫描发现的问题,转给对应业务的测试判断是否是问题;
- 如果是问题转给对应开发进行修复;
- 后续问题修复完成关闭问题,入库归档,沉淀为数据;
- 项目上线前,测试同学查看本次涉及到的接口的扫描情况来评估是否有安全问题,做补充测试或者修复后的回归。
通过接口越权平台的扫描以及研发和安全部门同学的支持,这套机制运转良好,1个月时间,能先于测试同学手工测试发现安全问题20+。逐步形成了适合有赞业务体系的越权防控系统,进一步减少测试人员的投入,也强化了安全质量规范的落地。同时,培养了研发同学的安全意识,在代码编写和用例设计阶段就充分考虑安全性,产生了很大的价值。
七、后续展望
- 目前接口越权扫描还是单点运行,后续考虑接入持续集成和安全防护体系中,能够发挥更大的作用。
- 每种业务的越权对象和越权定义都不一样,目前做的判断还是比较粗糙的,需要能够抽象出合理和通用的算法和规则,满足各种业务场景,比如按对象x操作的维度进行处理、规则可配置可插拔等。
- 通过规则的配置总体上还是不够智能,后续和安全以及其他部门同学合作,考虑采用新思路或者机器学习、人工智能等技术来提高识别率和准确性。
感兴趣的商家,可点击 → 免费试用有赞店铺~