不就是個簡訊登入API嘛,有這麼複雜嗎?

ThoughtWorks發表於2019-04-24

引子

上聯:這個需求很簡單

下聯:怎麼實現我不管

橫批:今晚上線

Part 1:暴力破解

早上開完站會,小李領了張新卡,要對登入功能做升級改造,在原來只支援使用者名稱密碼登入模式的基礎上,新增手機號和簡訊驗證碼登入。

不就是個簡訊登入API嘛,有這麼複雜嗎?

業務分析師薇薇早就準備好了故事卡,並且也考慮到這個功能的特殊性,除了平常的業務性驗收標準外,還專門新增了一些和安全有關的條目。這張故事卡看上去是這樣的:

故事卡-274

作為使用者,我可以通過手機號和簡訊驗證碼登入,以便於我更方便的登入。

安全驗收標準:

  • 簡訊驗證碼有效期5分鐘
  • 驗證碼為4位純數字
  • 每個手機號60秒內只能傳送一次簡訊驗證碼

小李看到故事卡中提到,驗證碼長度只有4位而且還是純數字,隱約覺得強度有些不夠,擔心萬一黑客來個多執行緒併發請求,或者拿一個叢集來暴力登入,有可能會趕在有效期內破解出合法的驗證碼。

小李把自己的擔心講給了業務分析師薇薇,並且建議把驗證碼長度增加到6位,或者在保持4位長度的情況下,改為數字和字母的組合,目的是增加驗證碼複雜性,提高暴力破解的門檻。

薇薇聽了這兩種選擇後直搖頭,說道:“我理解你的擔心,可是業務部門那邊的需求很明確,就是為了優化使用者登入體驗,所以才決定做手機號和驗證碼登入,如果把驗證碼弄的這麼複雜,那使用者體驗也好不到哪裡去,不符合這個故事卡的初衷啊。”

“對於使用者而言,4位數字驗證碼確實好記好填,可是對於黑客而言,就能很容易的完成暴力列舉,理論上最多1萬次請求就能遍歷完所有的驗證碼,更何況黑客沒那麼倒黴,要嘗試到第1萬次才猜對……”,小李說道。

為了滿足使用者體驗而在安全性上做出妥協,這種事情小李覺得自己無法說服自己,正準備掏出紙和筆跟薇薇做詳細解釋黑客攻擊手段的時候,團隊技術負責人老羅聽見了他們兩的討論,慢慢脫下帽子,摸了摸正在朝著“地中海”模式演進的烏黑的秀髮,說道:“那啥,伺服器在驗證登入請求的時候,不管驗證碼匹配還是不匹配,存在Redis裡的驗證碼只要被取出來就立即作廢,根本不給黑客暴力破解的機會。”

不就是個簡訊登入API嘛,有這麼複雜嗎?

小李的團隊已經搭建好了Redis,用來儲存登入過程中發給使用者的簡訊驗證碼,是一個手機號和驗證碼的鍵值對。

“對啊”,小李感覺眼前一亮,說道,“伺服器在比對請求中的驗證碼和Redis中儲存的這個使用者手機號所對應的驗證碼的時候,如果發現不匹配,那依然還是直接把Redis中的這個驗證碼作廢。這樣黑客發第二次登入請求的時候,會因為Redis中找不到對應的記錄而登入失敗。這樣既避免了暴力列舉攻擊,同時也不再需要增加驗證碼的強度,導致使用者體驗的下降了。”

小李建議把剛才的討論結果寫到故事卡里,而薇薇提議能否不要立即作廢:“萬一使用者輸入驗證碼的時候手滑輸錯了,豈不是要等幾十秒的時間再重新發第二個驗證碼?”

“可以做到驗證碼3次輸入錯誤後就作廢嗎?”薇薇問到。

“可以的,這個不難”小李堅定的回答到。

“好,那我們加一條安全驗收標準吧”,薇薇邊說邊修改了故事卡,新增加了一條:

儲存於伺服器端的驗證碼,至多可被使用3次(無論和請求中的驗證碼是否匹配),隨後立即作廢,以防止暴力攻擊

“對了小李”,老羅喝了口咖啡,最近連續的加班讓老羅感覺很疲憊,只能靠喝咖啡強打精神。“60秒內只能發1次簡訊那條,別忘了前後端都要做檢查。”

“知道知道,前端做不做都無所謂,關鍵是在後端要做限制。”小李連連點頭。

“好,那就這麼做,去忙吧”。老羅轉身坐下,正準備繼續剛才被打斷的工作,此時一個念頭快速在腦海裡一閃而過,老羅在電腦上開啟簡訊登入的這張故事卡,從頭到尾又看了一次,最後目光停留在“簡訊驗證碼有效期5分鐘”這條驗收標準那裡。

“簡訊每60秒發一次”,老羅心想:“但有效期是5分鐘,那第61秒的時候假如又請求傳送一次驗證碼,這時第一次傳送的驗證碼還沒過期,伺服器端該怎麼處理這個請求會比較穩妥呢?

顯然,第二個驗證碼直接覆蓋掉第一個會更加安全,也就是至始至終都只有一個處於有效狀態的驗證碼,但這會不會給使用者帶來困惑?畢竟偶爾還是有手機訊號不好,等了1分鐘多鍾之後才收到第一個驗證碼的情況。

如果不替換而是追加驗證碼呢?最極端的情況是會出現一個手機號有5個有效驗證碼的情況,會增加黑客暴力破解的成功概率。不過因為一個驗證碼最多隻能被使用3次,之後就被作廢了,所以實際上黑客暴力破解的難度依然很高。

總的來說,直接覆蓋的做法使用者體驗不佳但更安全,依然有效的做法使用者體驗更好但相對而言安全性略有降低。”

經過反覆思考後,老羅最終選擇保留驗證碼5分鐘有效期的設定。

Part 2:防不勝防

簡訊驗證碼登入的功能上線後,執行狀態一直比較平穩,然而這種平靜的氛圍被一通電話打破了。

不就是個簡訊登入API嘛,有這麼複雜嗎?

“喂,對,是我”,老羅桌上的電話響了,他忙著寫程式碼,歪著脖子用肩膀和臉夾住話筒說道:“是客服部啊,有什麼事我可以幫忙的?”

“是這樣,我們今天突然收到很多顧客打來的電話,抱怨說收不到簡訊驗證碼,登入不了賬戶,他們基本都是新使用者,只有用手機註冊的賬號,沒有使用者名稱密碼,所以也不能用原先的使用者名稱密碼去登入賬號。我們只好讓顧客再等會兒試試,可能是訊號不好,但後來他們反饋說還是收不到我們的簡訊,而且只是收不到我們的簡訊,所以,你們那邊能幫忙看看是怎麼回事嗎?”電話那邊一口氣講了一堆話。

“還有這種事,行,我知道了,我們馬上調查分析一下。”老羅剛結束通話電話,運維部的同事過來找到老羅,說簡訊配額今天消耗得很厲害,已經觸發了2次告警了,運維同事做了一下簡單的分析,發現早上10點和下午2點左右有兩批次大量傳送登入簡訊驗證碼的請求,但又沒有觀察到對應的後續登入請求,判斷可能是被黑客攻擊了,於是臨時性的遮蔽了攻擊來源IP地址的訪問。

“來找你就是想和開發團隊共同調查下這個問題,看接下來怎麼處理會比較好。”運維部的同事說道。

老羅覺得這個事和剛剛接到的客服部門說的是同一件事,便把剛才電話裡聽到的資訊和運維同事講了一遍。

“這更能證實是黑客攻擊了,而且看來他們的目標應該不是暴力登入,而是故意消耗簡訊傳送配額,一旦配額被用完,使用者就無法正常登入,也算是某種程度上的拒絕式服務攻擊了。” 運維部的同事說完看向老羅。

老羅若有所思的說道:“沒想到他們還能這麼玩兒。我們目前只限制了一個手機號60秒內發一次驗證碼,卻沒有應對大量不同手機號的情況。”

“那現在怎麼處理比較好呢?雖然臨時禁用了攻擊者的IP,但我們擔心會誤傷真實使用者,而且黑客也可能會變換IP來繼續進行攻擊。”運維同事繼續問道。

“有辦法,在發簡訊驗證碼之前先要求輸入圖形驗證碼。”

“嗯,有道理,你們什麼時候能做好上線?”

“我現在就加”,老羅還沒說完就已經開始寫程式碼了:“一會兒弄完緊急上線。”

“行,我回去安排一下,我們們運維部全力配合。”

“看來之前那張故事卡里的安全驗收標準還差了一條”,老羅自然自語道:“如果加上一條圖形驗證碼的要求恐怕就不會出這個事兒了。”

傳送簡訊驗證碼之前,先驗證圖形驗證碼是否正確

Part 3:權衡

“喂喂喂,這搞的什麼鬼?”使用者體驗設計師Jenny抓住路過的老羅說:“我不過就是休了兩天假,回來之後怎麼發現登入這裡多了個圖形驗證碼出來?”

不就是個簡訊登入API嘛,有這麼複雜嗎?

老羅向Jenny解釋了這個圖形驗證碼的由來,是出於安全的考慮才增加的。

“我知道安全很重要,可是這圖形驗證碼太傷害使用者體驗了,現在顧客登入過程中就要再多做一次輸入,如果填錯了還得重新再來一次,而且這圖形驗證碼的風格和我們的App風格明顯不匹配,另外,這圖形驗證碼是不是也太扭曲了,我都看錯好幾回了……。”Jenny顯然並不認同這個方案。

“風格我們可以修改,這不是還有你嘛。”老羅為難的說到:“難度高是因為現在的影像識別技術突飛猛進,簡單圖片驗證碼很容易被破解。”

“莫非就沒有別的解決辦法了嗎?”Jenny繼續問道。

“其實也有,就看公司舍不捨得花這筆錢了。”老羅接著說:“登入介面可以動態決定是否要求輸入圖形驗證碼,對於正常使用者可以讓他們無需輸入圖形驗證碼,對於黑客或者疑似黑客的人,就要求他們輸入。”

“這聽上去很好啊,另外,這和舍不捨得花錢有什麼關係?”Jenny不太明白。

“要動態決定是否要求輸入圖形驗證碼這件事兒,其實就是判斷當前用我們App的人是真實的顧客還是黑客。我們自己沒這個判斷能力,不過有提供這種服務的第三方API,只是他們都不是免費的,得花錢買。”老羅向Jenny解釋到。

阿某雲和騰某雲等等都提供這類服務,其主要原理是,伺服器在處理登入請求的時候,先儘可能多的收集該請求的上下文資訊,例如登入請求的來源IP地址,時間,手機號,User-Agent等等資料,並且把這些資料傳遞給第三方API,由他們進行一次分析判斷,並把結果返回給伺服器,告訴伺服器當前請求者是可信使用者還是可疑使用者。最終是否允許登入成功的決定權還是在伺服器這邊,只是藉助了第三方API提供的分析結果來做判斷而已。

不就是個簡訊登入API嘛,有這麼複雜嗎?

“我不懂技術,不過好像也聽懂了的樣子。"Jenny笑著說道。

“用第三方API做登入判斷這事兒我拍不了板,得找領導批准,說不定還得走採購流程。”但老羅覺得這條路的方向是對的。

“走,我們去問問領導的意見,我實在受不了現在這個圖形驗證碼。”Jenny拉著老羅徑直朝著總經理辦公室走去。

尾聲

最終,老羅他們團隊用上了某雲的第三方API做登入防護,去掉了令Jenny抓狂的圖形驗證碼。經過和業務部門的商量,驗證碼有效期最後縮短到了2分鐘。

在這期間還出現了兩個小插曲。運維部門的同事偶然間發現,應用程式日誌檔案里居然儲存了所有使用者的簡訊驗證碼,這是小李當初做除錯的時候加上去的,後來忘記關掉了。好在並沒有造成洩露,後來團隊修復了這個問題。

另一個小插曲是,團隊做了微服務架構改造,把傳送簡訊的功能拆分出來做成了一個獨立微服務,但卻沒有給這個新的介面設定好訪問控制許可權,以至於任何人在無需登入的情況下,只要向這個介面發起請求就能成功傳送一條簡訊給任意手機,簡訊內容還可以自定義。這個問題在安全團隊做滲透測試的時候發現的,嚇得老羅渾身冒冷汗。所幸發現及時,做了緊急修復,並沒有造成安全事故。

薇薇後來把簡訊登入的故事卡作為案例儲存了起來,把安全驗收標準又重新做了一次梳理,所以最終的故事卡是這樣的:

故事卡-274

作為使用者,我可以通過手機號和簡訊驗證碼登入,以便於我更方便的登入。

安全驗收標準:

  • 簡訊驗證碼有效期2分鐘
  • 驗證碼為6位純數字
  • 每個手機號60秒內只能傳送一次簡訊驗證碼,且這一規則的校驗必須在伺服器端執行
  • 同一個手機號在同一時間內可以有多個有效的簡訊驗證碼
  • 儲存於伺服器端的驗證碼,至多可被使用3次(無論和請求中的驗證碼是否匹配),隨後立即作廢,以防止暴力攻擊
  • 簡訊驗證碼不可直接記錄到日誌檔案
  • 傳送簡訊驗證碼之前,先驗證圖形驗證碼是否正確(可選)
  • 整合第三方API做登入保護(可選)

沒成想,一個簡訊登入API背後,還能牽扯出這麼多事兒來。


文/ThoughtWorks馬偉

更多精彩洞見,請關注微信公眾號:ThoughtWorks洞見

相關文章