逻辑漏洞

作者 ro0t 于 2021-06-01 发布
预计阅读所需时间 14 分钟
4.4k

逻辑漏洞

找回密码

找回密码相关的安全问题,基本上都是逻辑问题。找回密码的途径一般有两种:

  1. 通过回答密保问题重置密码
  2. 通过绑定的手机/邮箱重置密码

​ 第一种没啥好说的,可以考虑猜解或者社工,有时候数据包里面可能自带了密保答案,比如在 js 里…第二种,现在一般都是往账号绑定的手机发一个 token,或者邮箱发一个重置密码的链接。而发给邮箱的重置密码链接,其实都包含了特殊的参数,本质上也是 token。所以不管是手机短信还是邮件链接,本文统一称为 token,故本文就以邮箱为例。

​ 而细分的话又有 2 种形式,一种是不需要输入账号绑定的邮箱,点击忘记密码,后台会自己从数据库中获取账户的邮箱,然后在用户点击确认之后,发送一个 token;另一种是要求用户输入此账号绑定的邮箱,然后再发 token。显然,前者的用户体验更好,也避免了用户不但忘记密码同时还忘记手机号/邮箱的尴尬局面。

客户端直接回显 Token

​ 有些程序会将 token 放在响应包中,这个时候只要拦截发送的请求,查看响应就可以获得 token 了。如果是后台自动获取的邮箱,那么就可以重置任意用户的密码。织梦 CMS 就出现过这样的漏洞。

**修复方法:**避免将 token 回显在响应包中,验证的比对应该放在服务端进行。

Token 可暴力破解

暴力破解可分为几种:

  1. 好且快的暴力破解:后端没有对 token 的尝试次数做限制时,则可暴力破解 token。知道 token 格式后,例如是 4 位数字、6 位数字等等,然后爆破就完事了。

  2. 差但快的暴力破解:后端未对错误次数做限制,只对 token 的有效期做限制,比如 30 分钟,如果是 4 位数字,就很容易爆破出来;对于更加复杂的 token,比如 6 位数字,攻击者可以考虑通过多线程、多进程、分布式等手段加快速度,赶在 token 失效之前尝试完毕(显然,这个速度是有上限的,取决于信道容量、网速、目标服务器的并发量)。

  3. 差且慢的暴力破解:后端未对错误次数做限制,只对 token 的有效期做限制(比如一天甚至无限),并限制了提交频率,比如一分钟只能提交(验证)5 次。这个限制会有效降低攻击者尝试的速度,但如果 token 的有效期足够长,攻击者还是可以慢慢试出来的,取决于攻击者在目标的预期成本。

**修复方法:**对 token 错误的次数做限制、按照业务需求,给定过期时间,限制提交频率,避免被爆破;必要时可以再加大其复杂度(6 位数字,甚至添加英文字母)。

Token 可重放

这个很好理解,如果 token 验证通过之后依然有效,只要它没被覆盖,攻击者获得之后就可以重置密码。

**修复方法:**token 一旦验证成功,就要销毁,防止重放攻击。

篡改 用户名或找回地址

  1. 点击忘记密码时,后端生成页面的时候就自动拿到了邮箱,然后放在前端展示,用户点击确定之后,后端收到一个请求,这个请求包含了用户名和前端传来的邮箱。如果后端直接使用这个用户名和邮箱,生成此用户重置密码的 token,并且发送这个邮箱去,那么就可以篡改了:
  2. 前端填受害者的用户名,这个时候前端给后端的邮箱是受害者的邮箱,然后拦截数据包,篡改里面的邮箱为自己的,这样后端会发送重置密码的 token 到自己的邮箱,就可以重置受害者的密码。
  3. 前端填自己的用户名,这个时候前端给后端的邮箱是自己的邮箱,然后拦截数据包,篡改里面的用户名为受害者的,就可以重置受害者的密码。
  4. 流程进行到最后一步,填密码的时候,前端会把用户名发到后端去,后端以这个用户名为准来重置密码,那么只要修改这个用户名就可以重置此用户的密码。
  5. 找回密码凭证发到邮箱中,url 中包含用户名以及 token,这个 token 可以用于修改其他用户的密码。即服务端只校验 token 是否有效,而不校验 token 是否对应特定某个用户,那么只要修改用户名就可以重置任意用户的密码。
    显然,这几种都可以重置任意用户的密码。

修复方法:生成 token 的时候,要验证用户名与邮箱是否对应;验证 token 的时候,注意用户名、token 要对应。

跳过 Token 的验证流程

填写 token 之后提交,后端收到 token 验证通过后,服务器返回类似 ok、1、success、true 这种的状态值。我们知道这个只需要拦截响应包就可以欺骗前端进入下一步,一般就可以修改密码了。填写新的密码之后,如果后端没有校验步骤是否进行到这一步,那么就可以跳过 Token 的验证流程,直接重置任意用户的密码。

**修复方法:**后端在进行找回密码的最后一步:覆盖数据库中的密码 之前,验证一下此账户找回密码的流程是否达到了最后一步(最好就是在整个流程中,每步都验证上一步的操作是否已经完成);可以缩短流程,把校验 token 和修改密码合并到一个步骤里。

session 复用

需要满足 2 个条件:

  1. 在用户进入到找回密码页面的时候,后端没有 set-cookie
  2. 后端在进行找回密码的最后一步的时候,从 session 中取的用户名

​ 攻击的时候,先用自己的账户找回密码,收到邮件的时候,后端的 session 里的用户是我们自己。接下来新开一个标签页,用受害者的用户名进行找回密码,此时用的还是上一步的 session-id,所以等到邮件发出之后,此时后端的 session 里的用户名就变成受害者了,然后打开刚才收到的链接进行重置密码操作就可以重置受害者的密码。可以自行搜索 wooyun-2014-085843,是一个比较经典的案例。

当然啦,这个也是任意用户密码重置。

**修复方法:**找回密码功能,如果要依赖 session 完成,那么一定要做好 session-id 的销毁机制,避免 session 里重要的值被覆盖。

Token 可预测

重置密码的 token 应该是极难预测的。但是有些实现的就不安全,例如:

  1. 使用时间戳作为 token
  2. 用户 id 或者时间戳 经过 md5/base64 作为 token
  3. 有个特定的 api 可以生成 token,若未授权,攻击者可以直接调用

如果后端没有验证找回密码流程,则找到规律后,直接伪造 url 就可以重置任意用户的密码;如果后端验证了找回密码的流程,就按照流程正常走,发完 token 后,猜解一下 token,完成任意用户密码重置。

**修复方法:**token 尽可能随机,不要有规律可循;token 尝试次数做限制;验证找回密码的流程是否到了最后一步(最好就是在整个流程中,每步都验证上一步的操作是否已经完成)。

用户骚扰

这里以绑定的手机号为例。

不断地重复找回密码的过程,让后端不断发送 token。如果后端不验证账户与手机号的话,则可以短信轰炸任意手机号;如果后端在验证了账户与手机号之后,才发送 token 的话,则只能轰炸指定的手机号。

**修复方法:**限制找回密码的频率;注意,攻击者可以恶意消耗找回密码次数,导致真正的用户无法使用找回密码功能,所以加验证码提高攻击者恶意消耗次数的成本,也是一个非常常见的措施。

重复注册

可以注册一个已存在的用户,导致密码被覆盖,但原用户的所有信息(用户的信息,姓名、身份证、手机号等等)却没有改变。

案例:wooyun-2014-088708

总结

找回密码是一个非常常见的功能,但不是那么容易写得完善的,需要注意的地方有很多,逻辑稍有不完善就会出现漏洞。

上面的几点都好理解,那么最后不妨再思考一个问题:假如有个网站,它的找回密码功能没有对 token 的有效时间加以限制,也就是说派发的 token 是没有过期时间的,但是有错误次数限制,如何看待这样的设计方案?

首先,过期时间实现起来要比次数限制简单,业务方一般喜欢做简单的;如果过期时间与次数限制只能做一个的话,对于防御爆破来说,肯定是次数限制来得彻底;但是只做次数限制的话,增加了 token 泄露的风险,因为 token 不会过期,在漫长的生命周期里有很大的可能泄露,而且不是所有的 token 都会被使用(用户可能手抖了或者是由攻击者发起的找回密码),如果还有 token 可重放的问题,那风险就更大了。所以认为这样的方案并不完美,但是可以接受。

验证码

客户端生成验证码

验证码由客户端生成,比如用 js,在客户端验证,可以直接伪造数据包绕过;或者发回给服务端校验,修改响应就轻易可以绕过验证码。

修复方法:验证码的生成、比对应该放在服务端进行。

验证码回显在响应中

有些程序会将验证码放在响应中(body、Set-Cookie),这个时候只要拦截发送的请求,查看响应就可以获得验证码了。

修复方法:避免将验证码回显在响应包中,验证的比对应该放在服务端进行。

验证码可以为空

后端没有考虑到验证码为空的情况,不传验证码可以直接通过验证码的认证。ecshop 曾经出现过此问题。

修复方法:后端的逻辑应该是,没有收到验证码,等同于验证码错误。

万能验证码

一般是开发人员测试时所用的测试验证码直接部署在线上,不用调用任何获取验证码请求的接口,直接输入该万能验证码,即可通过验证。

修复方法:上线之前去除所有测试、调试逻辑。

验证码可复用

对于一个 session,验证码可以重复使用,攻击者只需要保证 cookie 没变,这个验证码就基本没有用了。

修复方法:一个 session 的验证码,一旦使用过了就应该销毁。

验证码可被自动识别

最常见的,就是验证码图像过于简单,比如没有任何干扰线/扭曲的验证码。但是本文不打算讲验证码的识别方式,网上有很多资料,橘友们如果感兴趣的话可以看看这一篇:https://wooyun.x10sec.org/static/drops/tips-141.html

还有一类是,验证码图像复杂,但是存在其他特征可以间接识别。一个经典案例是:https://segmentfault.com/a/1190000021903141。这个验证码其虽然不好直接识别,但是可以利用 svg path 长度这一特征,将其映射到特定验证码图片的字符上,遇到特定长度的 svg path 就等于见到了特定的字符,那么就可以自动化识别了。拓展一下思路,类似的问题可能是:返回验证码的数据包大小存在特征、验证码字符固定的颜色…

修复方法:1. 保证验证码图像本身其复杂度(添加英文字母或者汉字、扭曲字体、加干扰线、加噪点、粘连,有必要可以上行为验证码);2. 实现的时候注意验证码是否有其他特征可以间接识别出每个字符。

验证码可暴力破解

验证码提交错误之后,后端没有把对应 session 的验证码重置,导致攻击者可以重复尝试同一个验证码,导致验证码可暴力破解
验证码所有的可能性应该是很大的,这样可以避免攻击者重复提交一个答案绕过。但如果验证码本质上是在做选择题(通常只有 A、B、C、D 四个选项),那么所有的可能性就只有 4 种。案例:wooyun-2013-025245。

修复方法:1. 一个 session 的验证码,一旦错误就应该销毁;2. 提升用户体验的时候,也要时刻注意验证码的空间大小,防止空间缩小导致验证码容易破解。

验证码功能开关可控

后端有关闭验证码的功能,但这个启用的变量可以由前端传递参数控制。为什么会有这种情况呢?可能是调试功能没有下掉;或者,有些验证码接口是 A 部门统一提供的,然后某个业务方用的某个前端框架是已经直接封装好了验证码功能,但是实际上又不需要使用它(比如内部的某个不重要的系统),就可能要求验证码接口有个关闭验证的参数。

案例:wooyun-2014-071289、wooyun-2013-026219、wooyun-2012-014563

修复方法:没有必要就别设置开关了;一定要设置开关的话,不能由前端控制;非要用前端控制的话。。。参数值加个密吧,但这个控制方式一定只能放在内网的系统上使用。

DDoS

有些验证码的实现是,前端传参中含有证码图片的大小,后端根据这个参数来生成验证码图片;加上后端没有限制验证码图片的大小,那么攻击者只需要生成超大的图形验证码,就很容易造成内存耗尽,达到拒绝服务的目的。

修复方法:验证码生成的一切逻辑应在后端完成;如考虑服务兼容性一定要接受前端传参时,也要对其进行大小限制。

验证流程的顺序有误

验证码在验证的时候,顺序/逻辑关系不正确,导致验证码失效。

如:先校验用户名和密码,然后再验证验证码是否正确,这明显逻辑顺序有误,验证码实际上没起到防护的作用。

修复方法:先校验验证码,再继续后面的业务逻辑。

支付漏洞

涉及交易相关的安全漏洞

修改支付的价格

如:修改支付的参数,直接修改价格

修改支付的状态

如:使用1元购买成功的状态,实现100元的购买

修改购买量为负数

造成线上数据污染

重放攻击【非并发】

如:多次发起支付请求, 达到花费1元,购买多次的效果

并发请求

同时发起大量相同请求,由于数据库没有加锁导致短时间内同一个请求成功执行多次,造成资产损失。

测试环境未下线

通过遍历请求的参数(如活动id等),可发现线上隐藏的 或 测试环境未下线 的充值接口,实现优惠价支付的目的


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !