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階段將網站安全建設做爲一個重點關注的對象。面對惡意攻擊,咱們也盡力下降了被攻擊的影響,採用多套方案儘快的解決了問題,沒有將漏洞留到次日。