网站被攻击记录
网站被攻击记录
2019年4月22日21时许,有同学反映我们的网站出现了访问缓慢等异常现象。查询后台与CDN记录我们发现有人通过网站提供的接口进行了攻击。惊闻此事,组员们都感到十分震惊与不解,并积极开展了抢修工作。我们制订了如下抢修方案,先快速修复确保网站能尽快恢复使用,再给出一个完善的解决方案。网站于22时20分重新上线恢复访问,但是仍有访问不稳定的情况。经过进一步抢修,网站于23日凌晨0时5分恢复正常访问,此事件到此获得了较完美地解决。
事件Timeline
事件的timeline如下:
事件详细描述
网站被攻击现象
网站被攻击的主要现象是有人非法调用我们的注册接口,输入了大量无效的用户信息,导致网站运行缓慢。经过调查,共有至多4个IP在23日20时开始非法请求超过20万次,共新建无效用户约14万个,发送了超过14GiB的数据。
网站漏洞
我们的网站在设计之初考虑到用户主要是学校学生,因此在部分安全方面上有所缺失。本次被攻击的漏洞是注册用户接口没有一个有效的验证措施,没有过滤非法请求,只对邮箱进行了正则验证。
解决方案
网站被攻击引起了我们开发小组极大的重视。考虑到晚上9点正是网站用户量较多的时间段,我们制订了快速修复,妥善解决的解决方案。管理后端和网站部署的刘峻辰尝试快速修复,尽快使网站尽可能多的功能恢复正常使用。管理前端的肖萌威和PM罗奥升寻找一个妥善的解决方案并与快速修复同时开始正式修复,待夜深人静时再进行部署。
事件影响
事件造成了一定的损失。由于网站数据库回档到了先前的备份,导致8时至10时20分之间注册的用户账号,发表的评论丢失。这对于网站的宣传也有一定的负面影响。
详细解决方案
快速解决方案
显然,网站下线时间越长越容易导致用户的流失。为此,我们决定尽快修复网站功能,快速上线。经过简单分析,我们认为当前问题主要可以分成两个部分:恢复数据库和阻止非法链接。
恢复数据库
我们在设计网站时考虑到了数据库的备份问题,采用crontab定时指令的方式进行备份。我们发现8点的备份数据尚未收到影响,因此决定回滚到8时的数据。尽管我们对于用户的大部分请求都做了日志记录,但是我们并没有保存请求的具体内容,因此无法通过这些信息进行进一步的精确回档。这也是我们下一个阶段要改进的内容。
利用CloudFlare初步阻挡攻击
我们的网站使用了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阶段将网站安全建设作为一个重点关注的对象。面对恶意攻击,我们也尽力降低了被攻击的影响,采用多套方案尽快的解决了问题,没有将漏洞留到第二天。