網站被攻擊記錄
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階段將網站安全建設作為一個重點關注的對象。面對惡意攻擊,我們也盡力降低了被攻擊的影響,采用多套方案盡快的解決了問題,沒有將漏洞留到第二天。