0x00

最近一段时间,做了一些自动化/半自动化的测试小工具,主要还是想从重复的工作中解放出来,把时间投入到更有意义的事情中。

开发语言也从之前的 Python 逐步切换到了 Rust,虽然 Python 是一门很棒的语言,可以让我快速的实现各种想法的原型,但是 Python 在工程化的时候,总会出现一些不尽人意的地方。恰巧之前也学过一些 Rust,便逐步将主力开发语言切换到了 Rust。关于这部分的内容,后续可以再单独讲讲。

在最近的一些测试工作中,很大一部分目标都是各类 CMS、WMS 系统,经过前期的一些人工测试,发现这类业务逻辑相对简单且以 CURD 为主的系统,很难出现一些高危的 Web 漏洞(特指常见高危Web漏洞,例如:命令执行、SSRF、SQL注入等)。主要的风险还是以水平越权和垂直越权为主。

很多人可能根据经验会有疑问,为什么很少甚至不会出现 SQL注入漏洞?
在大型甲方企业的开发流程中,开发这类系统时,一般都不是从零开始写第一行代码的,而是使用该团队积累下来的各种二方包。对于常见的业务操作,例如 CURD、OSS上传等等,都有较为成熟的框架和解决方案。一旦出现过一次漏洞,通过对二方包的修复,很容易就实现了对所有其他系统的修复,以及在新系统中的预防。
顺便一提,对于其他类型的漏洞,例如 SSRF 和 命令注入等,在常规的 CURD 类的 CMS、WMS 系统中,根本不会出现相关的业务场景,所以更不会出现相关的漏洞了。

越权检测一直是扫描器领域长盛不衰的话题,目前在越权检测这个方向下面,还没有看到非常成熟的方案,也没有通用的银弹。

虽然没有银弹,但是一定有人已经造过轮子了,先去搜索了一些 Burp 上比较知名的越权测试插件,使用了一圈下来感觉都很不爽。有些界面丑陋且已经年久失修无法使用,有些逻辑复杂操作反人类,终于还是决定写一个自己用起来顺手的小插件。

0x01

在正式开始造轮子之前,我们先回顾一下手动测试越权的流程。

假设我们使用两个相同权限的账号 A 和 B,分别使用不同的浏览器登录。Chrome 登录账号 A,Edge 登录账号 B,给 Chrome 浏览器添加代理,把流量转发到 Burp。

在 Burp 中发现一个接口:http://api.example.com/user/get?id=10001 ,分析后发现是查看当前用户信息的接口。此时如果想测试越权,我们需要找到账号 B 中,此接口对应的参数,并将其复制过来到 Burp 中。假设 B 账号得到的参数为 id=51234 ,在 Burp 中修改参数后发送,并人工检查 Response 中是否含有 B 账号的数据。

在这个过程中需要来回切换多个窗口,复制粘贴不同账号的数据,最后还需要人工对比 Response 数据的内容,需要操作很多个步骤,并且非常的麻烦。另外这只是进行了两个同权账号的测试,如果系统的角色非常复杂,有管理员、普通会员、子账号的情况下,需要手工处理的工作就更多了。


测着测着,很自然的就出现了第二个想法:如果不替换参数,直接替换身份凭证是否可行?

答案自然是可行的,我们不用再去复制 B 账号每个接口的参数,只需要登录一次 B 账号并获取到 B 账号的身份凭证信息(Cookie、X-ACCESS-TOKEN等),在每次测试 A 账号的时候,顺手把 A 账号的身份凭证修改为 B 账号,就相当于以 B 账号的身份使用 A 账号的参数发起请求,直接查看返回是否一致就可以了。我们不需要来回的切换窗口,完全在 Burp 一个窗口中操作就可以了,当 Response 过大不方便肉眼看的时候,甚至可以使用的 Burp 的 diff 功能检查两个包是否有相同数据。

当然这个方案也有弊端:

  • 如果参数中、头中含有 CSRF Token 或类似的标记,此方法无法通过服务端的相关校验;
  • 依然无法避免角色过多带来的重复操作问题,同一个接口,每一个角色都要替换数据发一次包;
  • 在少部分业务场景下,如果参数本身是无规则字符串,例如 UUID、MD5 等情况且根本没有接口可以获取到其他账号的资源 ID,按照业务设计规则,可能不涉及越权问题;
    • 这一点实际上是有分歧的,理论上来说,我使用其他账号的资源 ID 获取到了资源内容,本身就属于越权,只能最终与业务沟通并根据实际情况再做是否修复的决定。

到目前为止,我们有了一些进展,简化了替换参数的工作量。但是我仍然希望将这个过程自动化,在我每次测试其他漏洞的时候,Burp 自动化帮我替换身份凭证,并比对返回值是否相似。

0x02

我们首先要解决的问题,就是账号的替换的问题,对同一个接口的请求,要以何种策略进行重放,是这个插件的核心逻辑。

回顾一下正常的测试流程,为了测试水平越权和垂直越权,我们至少需要准备3个账号,如果我们只想测试水平越权,或只想测试垂直越权,那么准备两个账号就可以了:

  • A 类账号:基准账号;
  • B 类账号:同权账号,与 A 类账号同权限,用于测试水平越权;
  • C 类账号:低权、无权账号,比 A 类账号权限低,用于测试垂直越权;

有了账号,下一步需要确认的就是重放顺序。对于查询数据的请求而言,重放顺序并不那么重要,无论哪个账号先执行请求,都不会影响返回结果。而对于创建资源、修改资源、删除资源之类的请求,顺序就非常敏感了。比如针对一个删除操作的越权测试,如果我们先使用 A 类基准账号测试,那么无论是否有漏洞,C 类账号的操作一定是失败的,因为资源已经被删除了,也就没办法使用这个资源 ID 进行越权测试了。如果使用 B 类同权账号进行测试,假设存在水平越权漏洞,那么资源已经被 B 类账号删除,C 类账号操作也会失败,也无法判断是否存在垂直越权了。

根据以上推论,我们可以规定出请求重放的顺序:C 类账号 > B 类账号 > A 类账号,在每次请求完成后,把响应结果记录下来,当所有的重放完成后,对响应内容进行相似度判断以检查是否存在越权漏洞。

现在看起来似乎没有什么问题了,但是我们还忽略了前面提到的一点,如果业务系统有更多的角色或者业务身份呢?比如超级管理员,小管理员,后台管理员,前台管理员,普通会员,游客等等。

首先我们先看看能否简化为ABC三类账号,比如有些系统虽然有很多角色,但是是通过为某个账号单独赋权得到的。举个例子,某个系统的账号必须归属于其中的一个角色,而每个角色类型可以单独设置权限点。虽然看起来这个系统非常复杂,有非常多的角色,但是实际上我们测试时,只需要一个开启所有权限点的角色,和一个没有任何权限点的角色。两个开启全部权限点的角色的账号分别作为 A 类账号和 B 类账号,没有权限点的角色可以作为 C 类账号,问题迎刃而解。

但是实际测试中发现,并不是所有的系统都可以这样处理,有些系统无论如何也无法简化为 ABC 三类账号,那又要怎么办?

此时我们只需要对插件的账号系统做一些小小的修改即可,添加一个 D 类账号,代表自定义角色。那么问题又来了,D 类账号的重放顺序应该如何定义呢?很明显当你遇到这么复杂的情况时,事先并不能确定应当如何重放,也就无法在插件中写死重放顺序,添加一个优先级功能根据实际情况判断是最好的做法了。

那么现在的账号体系就变成了下表这样,优先级越高请求顺序越靠前:

  • A类账号,优先级-1,基准账号,所有其他账号的 Response 均和这个做对比,优先级最低,一定最后一个重放请求;
  • B类账号,优先级50,同权账号,用于测试水平越权,优先级默认为 50,夹在低权和基准之间;
  • C类账号,优先级101,低权账号,用于测试垂直越权,一定第一个重放请求;
  • D类账号,优先级1~100,自定义角色,请求优先级可以根据实际情况自定义;

最后,再添加一个配置管理系统,给不同的站配置不同的账号,再加一点点界面,基本上就完成了。

0x03

每一个配置相当于一次测试项目,在同一时间,只能开启一个配置。

在每一份配置中,可以单独配置目标 scope、黑名单、重放设置等,防止触发重放各种不必要的请求;

image

同时账号也是在每一份配置中设置的,在这里填入需要替换的身份凭证信息,可以是 HTTP Header,也可以是某个参数。

image

当正确配置完成账号信息后,此时在浏览器中访问目标网站,AuthTest 将会使用配置好的账号自动重放这些请求。所有的请求都会在数据中心中展示。

在这里,可以对某些请求标记为漏洞,也可以将某些请求加入黑名单(比如图中出现的baidu统计请求),加入黑名单后,后续遇到该 URL 时将不再重放。

image

点击某一行即可看到详细的请求信息,相似度为每一个包与基准账号的 Response 做对比,使用了编辑距离算法。

image

经过一些实际测试,确实解放了工作中很大一部分的劳动力,减少了很多的重复性工作,漏洞挖掘效率也得到了显著提升。

不过由于该工具还在完善阶段,并且很多功能是针对个人使用习惯高度定制化的,暂时没有开源的打算,后续也会继续分享一些最近一段时间开发的其他小工具,以及一些安全建设理念。等到小工具打磨成熟了,会适时的放出来与大家一起使用。