网站被攻击记录

网站被攻击记录

2019年4月22日21时许,有同学反映我们的网站出现了访问缓慢等异常现象。查询后台与CDN记录我们发现有人通过网站提供的接口进行了攻击。惊闻此事,组员们都感到十分震惊与不解,并积极开展了抢修工作。我们制订了如下抢修方案,先快速修复确保网站能尽快恢复使用,再给出一个完善的解决方案。网站于22时20分重新上线恢复访问,但是仍有访问不稳定的情况。经过进一步抢修,网站于23日凌晨0时5分恢复正常访问,此事件到此获得了较完美地解决。

事件Timeline

事件的timeline如下:

timeline.png

事件详细描述

网站被攻击现象

网站被攻击的主要现象是有人非法调用我们的注册接口,输入了大量无效的用户信息,导致网站运行缓慢。经过调查,共有至多4个IP在23日20时开始非法请求超过20万次,共新建无效用户约14万个,发送了超过14GiB的数据。

流量.PNG

来源.PNG

网站漏洞

我们的网站在设计之初考虑到用户主要是学校学生,因此在部分安全方面上有所缺失。本次被攻击的漏洞是注册用户接口没有一个有效的验证措施,没有过滤非法请求,只对邮箱进行了正则验证。

解决方案

网站被攻击引起了我们开发小组极大的重视。考虑到晚上9点正是网站用户量较多的时间段,我们制订了快速修复,妥善解决的解决方案。管理后端和网站部署的刘峻辰尝试快速修复,尽快使网站尽可能多的功能恢复正常使用。管理前端的肖萌威和PM罗奥升寻找一个妥善的解决方案并与快速修复同时开始正式修复,待夜深人静时再进行部署。

事件影响

事件造成了一定的损失。由于网站数据库回档到了先前的备份,导致8时至10时20分之间注册的用户账号,发表的评论丢失。这对于网站的宣传也有一定的负面影响。

详细解决方案

快速解决方案

显然,网站下线时间越长越容易导致用户的流失。为此,我们决定尽快修复网站功能,快速上线。经过简单分析,我们认为当前问题主要可以分成两个部分:恢复数据库和阻止非法链接。

恢复数据库

我们在设计网站时考虑到了数据库的备份问题,采用crontab定时指令的方式进行备份。我们发现8点的备份数据尚未收到影响,因此决定回滚到8时的数据。尽管我们对于用户的大部分请求都做了日志记录,但是我们并没有保存请求的具体内容,因此无法通过这些信息进行进一步的精确回档。这也是我们下一个阶段要改进的内容。

利用CloudFlare初步阻挡攻击

我们的网站使用了CloudFlare CDN进行加速,但是并没有开启严格的攻击防护。在本事件发生后,我们临时将防护等级调整到了最高,对所有请求都进行了一个js challenge。该操作成功阻挡了大量非法请求,但是用户在使用网站时会先被定向到一个验证页面,影响了用户的体验。实践证实,尽管使用的是CloudFlare的免费套餐,但是其也成功阻挡了攻击并找出了发起的IP。

威胁.PNG

快速恢复访问

在完成上面的工作以及简单调试后,我们快速的恢复了网站的访问,整个快速修复过程耗时约1小时,网站功能基本恢复正常。随后,我们投入了正式修复的工作。

网站的正式修复

尽管快速修复初步解决了问题,但是它也不是一个长久之计。为此,在进行快速修复的同时,其他成员也开始研究完善的修复方案。经过讨论,我们采取了腾讯防水墙作为验证模块。

方案设计流程

其实在Alpha开始的阶段,由于我们是个小网站,同时我们拿到的学长的代码也没有安全验证这一块,因此我们也没有考虑到安全验证这一块,但是在Alpha阶段的尾期想到了这一块,可能需要在注册的时候进行一定的验证来避免恶意的用户注册,于是在上周末已经进行了一部分的验证码的探究。

但是在今天网站遭受了比较严重的攻击,我们将这一功能提前上线。

验证码的选择

验证码的选择有很多种,我们最终选择了拼图类的验证码,毕竟这种验证码比传统的字母验证码的安全性还是要强一点,即使通过脚本来通过验证也是很费时的。而据我的简单了解,极验(geetest)的验证码就做的不错,博客园登陆时所弹出来的验证就是使用的极验的接口。

1555949867678.png

极验的验证码能做到对用户进行区分,对可信用户能够免验证通过。但是在后续的了解中,发现极验的使用可能稍微有点麻烦,注册账号时也存在着24h的审核期,不能够马上投入使用,因此对于极验的了解没有过多的深入,尽管它的功能实现可能更好好。因此我去了解了腾讯的验证码平台,并最终选择了腾讯。

腾讯验证码

简介

腾讯验证码平台也是一个提供验证码接口的网站,他提供了和极验类似的功能,同时使用起来也是比较的简单。

1555954724506.png

他也能够实现与极验类似的区分用户的功能。对于可信用户,可以直接通过验证,对于可疑用户需要采用拼图验证,对于恶意用户采用难度更高的立体图形验证。

1555950142055.png

对于恶意用户的验证码:

1555951510912.png

因此它十分方便于用户的使用。而它的安全性也是可以信任的,腾讯系的产品基本都是采用的腾讯验证码。

1555950196897.png

同时腾讯验证码免费提供每小时2000次验证,对于我们的小型网站来说绰绰有余,不需要考虑费用问题,注册也没有审核期,只需要手机、QQ号和网站地址即可轻松完成注册并立即开始使用。对于验证码的配置管理也十分简单,登陆后即可查看各种各样的数据,如每天的验证数据、拦截数据等等。进入配置中心后即可对验证码的外观、安全等属性进行配置,如开启可信用户免验证。因此我们最终就采取了腾讯的验证码。

1555953877511.png

1555955257840.png

验证码的使用

注册后将会获得一个验证码 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)

附:前后端调用时序图

img

正式恢复访问

前后端代码与23日0:01编写完成并调试通过。随后我们将CloudFlare的防护等级降低到Medium,并部署了正式修正版本。用户体验恢复正常。

结语

本次网站被攻击事件给我们的网站带来了不小的影响,造成了数据库被迫回滚,网站临时下线,也丧失了部分潜在用户。此次事故让我们深刻的意识到网站安全的重要性,我们也决定在Beta阶段将网站安全建设作为一个重点关注的对象。面对恶意攻击,我们也尽力降低了被攻击的影响,采用多套方案尽快的解决了问题,没有将漏洞留到第二天。

posted @ 2019-04-23 13:42  提不起劲想赶紧完工  阅读(1565)  评论(3编辑  收藏  举报