在这里分享一些之前对某网站会员/用户系统(一般域名都是passport.xx.com)进行漏洞检查查出的一些问题,这些问题大多都是逻辑类漏洞,利用漏洞进行攻击并不需要什么高深的技术能力,所以危害尤其大,把相关经验分享给大家希望大家可以自查。
这里要说明,下面很多漏洞的例子是基于已经获得有效的账号密码(行话说就是密正的账号)的前提的,大家可能会问:
1、怎么可能拿到别人的账号和密码?其实有很多网站都泄露过带明文密码的账号库(也有一些账号库是不带密码的,或者是MD5哈希后的密码,不带密码的可以用一些常见简单密码尝试,MD5后的密码可以用云MD5密码库来“解密”),很多人会用一些工具拿手头拿到的几百万个账号密码针对某网站进行逐一的登录测试(这个过程叫做扫号),遇到有验证码的网站使用程序自动识别密码,更高级一点可以使用云打码平台来识别验证码,只要能登录那么这个账号密码就是针对这个网站的密正账号,就可以以一定的价格卖给收号的人了(比如5元一个账号),扫号的人干的是纯技术活,至于收号的人拿到账号去干啥这就根据网站不同各不相同了(对于游戏网站的账号拿去后基本是洗号之类的)。
2、既然已经拿到了账号和密码什么都可以干了,还谈什么漏洞?很多网站虽然有账号密码可以登录,但是一些关键性操作往往有双重验证的(比如通过邮箱验证,通过手机验证),更何况有一些网站有账户安全策略,如果检测到账户不安全的话(比如是异地登录)可能会需要通过手机验证码验证后才能登录。所以一般情况下即使有别人的账号你也只能登录到他的后台看看,几乎不可能做什么敏感操作(比如提取账户余额、修改密码之类的),只有进一步把一些绑定换绑之后才可能有进一步的行为。
先验证后操作没有绑定在一起整体对待
假设自己的账号是A并且已经绑定过邮箱,手头密正的账号是B。
1、使用A账号登陆网站,在浏览器中开两个页签。
2、进入更换邮箱的功能,页面会提示需要获取验证码,点击获取后进入了“更换邮箱”界面,在这个界面中网站会要求你输入验证码以及希望更换的新邮箱。
3、刚才不是打开了两个标签页面吗,到第二个标签页面点击登出,使用B的账号登陆进入网站后同样进入更换邮箱的功能,点击获取验证码按钮。
4、 在第一个页签更换邮箱的第二步输入一个新的要更换的邮箱xx,然后输入正确的验证码(到A账号绑定的邮箱查看验证码)完成更换邮箱的流程(其实当前登陆的账户已是B)。
5、在第二个页签刷新一下页面,可以看到B的邮箱已经更换为xx。
也就是说更换邮箱这个操作的第二步直接读取了B的登陆信息进行了更换,而没有验证B已经不是当初提出更换邮箱申请的A了,没有把操作作为整体验证导致漏洞的发生。如果网站有这个逻辑漏洞,那么很可能更换手机功能也可以这么破。
安全相关的接口定义的太通用导致可以暴力破解
如果玩游戏可能知道有一种叫做密保卡的东西,比如是一个X*Y(比如10*10)的二维表格,每一格都是一个数字,玩家在做敏感操作的时候需要输入密保卡上指定位子的三个数字,全部输入正确后才可以进行操作。比如会要求你输入(A10,C2,F8)三个坐标的数字,你需要查看密保卡找到这三个数字依次输入,如果你没有密保卡,密保卡的数字范围是0-99那么每一个数字猜中的概率就已经是1%了,三个数字全部猜中的概率是百万分之一(左右),所以是一种简单且基本有效的安全验证方式。
现在AJAX技术用的很多,如果对于密保卡验证的操作也采用了一个类似checkmibao/?locations=A10,C2,F8&code=11,22,33这样的AJAX请求固然可以达到功能需求,但是有没有想过,这种API(如果没有请求频度限制)非常容易快速爆破,完全可以模拟这样的请求checkmibao/?locations=A1,A1,A1&code={0},{0},{0},参数{0}来一个循环从0到99(最坏的情况是尝试100次,这里要说明的是既然是爆破这里提供的三个坐标肯定是相同的坐标位置),如果返回的结果是正确的则A1的密保已经得出了,否则继续查询,对于表格的其它坐标A2-J10也同样进行同样的操作很快就可以把整个密保卡“计算”出来(如果不是那么贪婪的话可以拿到坐标后直接计算3个坐标,会快一点)。应该怎么改?API完全不应该设locations参数,因为用户在验证密保卡的时候系统是知道验证的三个坐标的,仅仅是checkmibao/?code=11,22,33这样就可以了。
其实说白了这个漏洞出现的原因是没有遵循“客户端的一切是不可信”的原则,只能相信服务端的数据,客户端提交的数据始终要带着怀疑的态度来处理,任何数据只能作为参考或呈现不能作为参数直接使用。除了这个问题之外,AJAX也可能会带来一些安全隐患,这种隐患和AJAX并无关系,而是很多开发人员在做AJAX接口的时候会错误的以API的心态去开发而忘记了接口是网站的一部分,比如:
1、 有的时候为了屏蔽一些数据的呈现(比如IP地址显示为192.168.*.*),只是在前端进行数据格式化,AJAX接口还是提供了原始数据192.168.0.1那么完全达不到安全的目的。
2、 AJAX接口往往轻量,刷起来也快,而且容易遗漏对访问频度的控制,更容易爆破。
3、 无刷新意味页面的初始加载和后续操作的验证会分段,分段就容易产生漏洞。
废弃的API导致的漏洞
现在很多网站都会制作APP版本,在测试此网站的APP后发现和服务端的交互参数都进行了签名+BASE64,我们知道安卓的应用程序是可以反编译的,经过反编译可以查看到:
1、公钥私钥都写在了Java代码里,使用公钥来加密要发送给服务端的数据,使用私钥来解密从服务端返回的数据,这样就可以通过翻译服务端返回的数据来熟悉API也可以绕过客户端直接发送请求给服务端。
2、了解了所有API后发现代码里有一个隐藏API没有用到,经过测试这是一个“内部使用”的API,可以通过这个API把某个客户端换绑到账户并且无需进行其它验证。至此整个网站的账号安全都因为这个点失守了。
这个过程看上去很简单,但要发现这个漏洞是经过了大量的尝试的,要先熟悉整个操作流程,逐个尝试是否有逻辑漏洞和其它突破点,每一个漏洞都是不同的不是每一个APP都会有隐藏的内部API,每一次可能的突破往往都是基于非常规思维。
那么改进方法也很简单,把KEY值以打散方式存储于SO文件中,最好协议的加密解密全采用C编写,在Java层对于协议都是透明的。
总结
可别小看这几个漏洞,出现这样的漏洞意味着黑客可以随便修改用户的邮箱、手机、密码、从而绕过各种二次验证,把账号完全掌握在自己手里。如果扫号的人手头有1000万个账号库,扫到密正账号50万个,想想也吓人。从这几点我觉得可以总结一下,对于网站的安全始终要记住几点:
1、木桶理论,整个网站99%的地方都很安全是不够的,如果网站的账户体系是打通的,那么只要有1%的地方有问题,其它99%的安全都是白搭,找漏洞的人往往也最喜欢找(由初级程序员开发的)边缘模块下手。
2、不要信任客户端的任何数据,对于每一项由客户端提交的数据我们都应该当做是用户的输入(哪怕这个数据本来就是从服务端读取的),进行严格的验证。
3、对于已经实现的逻辑要思考一下是不是有非常规的路径,是否有可以绕过的逻辑。
4、做好请求的频度监控以及控制,如果发现某些请求访问量徒增可能就会是一个漏洞点在被别人爆破。
5、重要的功能尽量不要去走AJAX,不是说AJAX有什么问题,而是AJAX容易让人放松警惕。如果可以的话让(AJAX)请求或API调用变得完全没有意义,比如使用统一的通用的AJAX API提供接口,比如http://passport.xxx.com/ajax?token=xxx,token是在服务端事先生成的对应了某个操作逻辑,使用后则失效,即使是相同的API token也是动态的(而不是为每一个业务分配一个具体的URL,这样只根据token根本就不知道是哪个业务,如果别人不能熟悉你的流程当然也很难去破解逻辑漏洞)
6、不苛求用户有很强的安全意识,作为网站的开发要主动保护用户的密码等敏感信息,提示用户经常修改密码,并为网站建立安全分析体系,对用户的操作进行安全评级,如有必要对用户的登录请求进行驳回。