2019年4月22日21时许,有同窗反映咱们的网站出现了访问缓慢等异常现象。查询后台与CDN记录咱们发现有人经过网站提供的接口进行了攻击。惊闻此事,组员们都感到十分震惊与不解,并积极开展了抢修工做。咱们制订了以下抢修方案,先快速修复确保网站能尽快恢复使用,再给出一个完善的解决方案。网站于22时20分从新上线恢复访问,可是仍有访问不稳定的状况。通过进一步抢修,网站于23日凌晨0时5分恢复正常访问,此事件到此得到了较完美地解决。javascript
事件的timeline以下:html
网站被攻击的主要现象是有人非法调用咱们的注册接口,输入了大量无效的用户信息,致使网站运行缓慢。通过调查,共有至多4个IP在23日20时开始非法请求超过20万次,共新建无效用户约14万个,发送了超过14GiB的数据。前端
咱们的网站在设计之初考虑到用户主要是学校学生,所以在部分安全方面上有所缺失。本次被攻击的漏洞是注册用户接口没有一个有效的验证措施,没有过滤非法请求,只对邮箱进行了正则验证。java
网站被攻击引发了咱们开发小组极大的重视。考虑到晚上9点正是网站用户量较多的时间段,咱们制订了快速修复,妥善解决的解决方案。管理后端和网站部署的刘峻辰尝试快速修复,尽快使网站尽量多的功能恢复正常使用。管理前端的肖萌威和PM罗奥升寻找一个妥善的解决方案并与快速修复同时开始正式修复,待夜深人静时再进行部署。python
事件形成了必定的损失。因为网站数据库回档到了先前的备份,致使8时至10时20分之间注册的用户帐号,发表的评论丢失。这对于网站的宣传也有必定的负面影响。数据库
显然,网站下线时间越长越容易致使用户的流失。为此,咱们决定尽快修复网站功能,快速上线。通过简单分析,咱们认为当前问题主要能够分红两个部分:恢复数据库和阻止非法连接。json
咱们在设计网站时考虑到了数据库的备份问题,采用crontab定时指令的方式进行备份。咱们发现8点的备份数据还没有收到影响,所以决定回滚到8时的数据。尽管咱们对于用户的大部分请求都作了日志记录,可是咱们并无保存请求的具体内容,所以没法经过这些信息进行进一步的精确回档。这也是咱们下一个阶段要改进的内容。后端
咱们的网站使用了CloudFlare CDN进行加速,可是并无开启严格的攻击防御。在本事件发生后,咱们临时将防御等级调整到了最高,对全部请求都进行了一个js challenge。该操做成功阻挡了大量非法请求,可是用户在使用网站时会先被定向到一个验证页面,影响了用户的体验。实践证明,尽管使用的是CloudFlare的免费套餐,可是其也成功阻挡了攻击并找出了发起的IP。安全
在完成上面的工做以及简单调试后,咱们快速的恢复了网站的访问,整个快速修复过程耗时约1小时,网站功能基本恢复正常。随后,咱们投入了正式修复的工做。服务器
尽管快速修复初步解决了问题,可是它也不是一个长久之计。为此,在进行快速修复的同时,其余成员也开始研究完善的修复方案。通过讨论,咱们采起了腾讯防水墙做为验证模块。
其实在Alpha开始的阶段,因为咱们是个小网站,同时咱们拿到的学长的代码也没有安全验证这一块,所以咱们也没有考虑到安全验证这一块,可是在Alpha阶段的尾期想到了这一块,可能须要在注册的时候进行必定的验证来避免恶意的用户注册,因而在上周末已经进行了一部分的验证码的探究。
可是在今天网站遭受了比较严重的攻击,咱们将这一功能提早上线。
验证码的选择有不少种,咱们最终选择了拼图类的验证码,毕竟这种验证码比传统的字母验证码的安全性仍是要强一点,即便经过脚原本经过验证也是很费时的。而据个人简单了解,极验(geetest)的验证码就作的不错,博客园登录时所弹出来的验证就是使用的极验的接口。
极验的验证码能作到对用户进行区分,对可信用户可以免验证经过。可是在后续的了解中,发现极验的使用可能稍微有点麻烦,注册帐号时也存在着24h的审核期,不可以立刻投入使用,所以对于极验的了解没有过多的深刻,尽管它的功能实现可能更好好。所以我去了解了腾讯的验证码平台,并最终选择了腾讯。
腾讯验证码平台也是一个提供验证码接口的网站,他提供了和极验相似的功能,同时使用起来也是比较的简单。
他也可以实现与极验相似的区分用户的功能。对于可信用户,能够直接经过验证,对于可疑用户须要采用拼图验证,对于恶意用户采用难度更高的立体图形验证。
对于恶意用户的验证码:
所以它十分方便于用户的使用。而它的安全性也是能够信任的,腾讯系的产品基本都是采用的腾讯验证码。
同时腾讯验证码免费提供每小时2000次验证,对于咱们的小型网站来讲绰绰有余,不须要考虑费用问题,注册也没有审核期,只须要手机、QQ号和网站地址便可轻松完成注册并当即开始使用。对于验证码的配置管理也十分简单,登录后便可查看各类各样的数据,如天天的验证数据、拦截数据等等。进入配置中心后便可对验证码的外观、安全等属性进行配置,如开启可信用户免验证。所以咱们最终就采起了腾讯的验证码。
注册后将会得到一个验证码 APP ID和一串密钥 App Secret Key。
腾讯验证码首先在前端进行验证,经过验证后会生成一个票据和一个随机串,将票据和随机串发送到后端后,由后端将票据、随机串和密钥发往腾讯服务器进行再次验证,所以只要密钥不被泄露,理论上是很难强行突破验证的。
验证码的使用分为前端和后端。
前端功能很简单,就是添加对应的元素,可以弹出验证框,再将票据、随机串和用户IP传回后端服务器便可。
a、在Head的标签内最后加入如下代码引入验证JS文件(建议直接在html中引入)。
<script src="https://ssl.captcha.qq.com/TCaptcha.js"></script>
b、在你想要激活验证码的DOM元素(eg. button、div、span)内加入如下id及属性,data-appid的内容即为验证码 App ID。
<!--点击此元素会自动激活验证码--> <!--id : 元素的id(必须)--> <!--data-appid : AppID(必须)--> <!--data-cbfn : 回调函数名(必须)--> <!--data-biz-state : 业务自定义透传参数(可选)--> <button id="TencentCaptcha" data-appid="App ID" data-cbfn="callback" >验证</button>
c、为验证码建立回调函数,注意函数名要与上面的data-cbfn
相同,这里对于验证成功后的操做能够进行必定的修改。
window.callback = function(res){ console.log(res) // res(用户主动关闭验证码)= {ret: 2, ticket: null} // res(验证成功) = {ret: 0, ticket: "String", randstr: "String"} if(res.ret === 0){ alert(res.ticket) // 票据 } }
完成以上操做后,点击激活验证码的元素,便可弹出验证码。
对于验证码进行操做时会生成一个res对象,用户直接关闭验证码时,其内容为{ret: 2, ticket: null}
,当验证成功时,其内容为{ret: 0, ticket: "String", randstr: "String"}
,ticket为票据,randstr为一串随机串。经过ret的值就能判断是否验证经过。验证经过后咱们须要将这两项和用户IP传回后端,由后端进行二次验证。
在完成快速修复任务后,后端开发也加入了正式修复流程。因为前端已经摸清了该验证模块的逻辑,找到了一份能够用来参考的python2 教程,后端的工做压力较小。再将py2样例移植到py3上后,通过简单调试就能够成功执行。惟一遇到的坑就是腾讯的接口文档和样例中都代表返回值是一个int,1表示认证成功,-1表示认证失败,然而实际上接口返回的是字符串'1'和'-1'。具体设计以下:
在验证完成后,客户端收到得到一个验证票据(ticket)。将票据上传至服务器,并发送GET请求到下方接口能够校验验证码的票据,判断当次验证是否成功。
URL: https://ssl.captcha.qq.com/ticket/verify
字段名 | 描述 |
---|---|
aid (必填) | APP ID |
AppSecretKey (必填) | 密钥 |
Ticket (必填) | ticket |
Randstr (必填) | randstr |
UserIP (必填) | 用户IP |
返回值
Json格式,eg:{response:1, evil_level:70, err_msg:""}
字段名 | 描述 |
---|---|
response | 1:验证成功,0:验证失败,100:AppSecretKey参数校验错误[required] |
evil_level | [0,100],恶意等级[optional] |
err_msg | 验证错误信息[optional],查看详细说明 |
至此,验证码接入已完成,还能够进行更加复杂的接入。
样例的Python2 代码以下,虽然问题不少但勉强能看,明显的错误已标出:
#!/usr/bin/python # -*- coding: utf-8 -*- import json, urllib from urllib import urlencode # 注: py3里面这个库位置换了 #---------------------------------- # 腾讯验证码后台接入demo #---------------------------------- #---------------------------------- # 请求接口返回内容 # @param string appkey [验证密钥] # @param string params [请求的参数] # @return string #---------------------------------- def txrequest(appkey, params={}, m="GET"): # 注: appkey和m实际没有用到 url = "https://ssl.captcha.qq.com/ticket/verify" if m =="GET": f = urllib.urlopen("%s?%s" % (url, params)) else: f = urllib.urlopen(url, params) content = f.read() res = json.loads(content) if res: error_code = res["response"] if error_code == 1: # 注: 这里应该是字符串'1' print "验证成功" else: print "%s:%s" % (res["response"],res["err_msg"]) else: print "请求失败" if __name__ == '__main__': AppSecretKey = "test"; # 注: 这个样例多了个分号 appid = "test" Ticket = "test" Randstr = "test" UserIP = "127.0.0.1" params = { "aid" : appid, "AppSecretKey" : AppSecretKey, "Ticket" : Ticket, "Randstr" : Randstr, "UserIP" : UserIP } params = urlencode(params) txrequest(AppSecretKey, params)
附:先后端调用时序图
先后端代码与23日0:01编写完成并调试经过。随后咱们将CloudFlare的防御等级下降到Medium,并部署了正式修正版本。用户体验恢复正常。
本次网站被攻击事件给咱们的网站带来了不小的影响,形成了数据库被迫回滚,网站临时下线,也丧失了部分潜在用户。这次事故让咱们深入的意识到网站安全的重要性,咱们也决定在Beta阶段将网站安全建设做为一个重点关注的对象。面对恶意攻击,咱们也尽力下降了被攻击的影响,采用多套方案尽快的解决了问题,没有将漏洞留到次日。