- 引言
- 天降飛鍋
- 攻防階段1:封IP
- 攻防階段2:關入口
- 攻防階段3:調整驗證方式
- 攻防階段4:升級驗證方式
引言最近處理了一起針對開源社區的攻擊行為,對方疑似動用黑產勢力,連續幾天發動境內外的肉雞IP發起大量注冊用戶請求,當然了,并不是真正的要注冊用戶,而是利用注冊用戶的動作,把短信驗證碼額度刷爆,最瘋狂的那天刷了一萬多條驗證短信,真是喪心病狂。
面對這些壞蛋,我們當然不能坐以待斃,經過幾次攻防交手,暫時是我們取得小勝。
本文分享這次的攻防經過,希望對其他使用Discuz系統的朋友能有所幫助。
天降飛鍋某天,突然收到短信運營商告警,在一天內被刷掉一萬多條短信驗證碼,這顯然有問題,如果這些都是真實注冊請求的話,那我肯定超級開心,可惜并不是。
攻防階段1:封IP經過對訪問日志的分析,發現短時間內有大量的境外IP請求用戶注冊接口,很快就把每天的短信驗證碼額度耗盡,導致正常的用戶注冊和登錄請求無法使用。
對于這種情況,第一時間想到的是封禁這些IP,把它們加到路由黑洞(/sbin/ip route add blackhole $IP)中,這樣做比用IPTABLE加防火墻規則效率更高,對服務器的性能損耗更小,而且不會給攻擊者回包,反過來影響其效率。
不過,這些專業的黑產勢力,顯然是有充足的肉雞資源,直接封IP的做法效果有限,還是無法阻止它們的攻擊。
攻防階段2:關入口黑產勢力實在太猖狂,除了封IP外,暫時還沒找到更好的辦法,只能先避其鋒芒,我惹不起還是躲得起的。因此決定暫時先關閉注冊入口,以及短信驗證碼方式登錄,只保留密碼登錄功能。
在Discuz管理后臺關閉注冊入口,如下圖所示:
![]()
調整完后,黑產的請求量大概下降了一半,不過這招相當于是殺敵一千,自損八百,用戶的有些功能受限了,不是長久之計。
攻防階段3:調整驗證方式在敵人的攻勢減弱后,就有更多時間思考和嘗試其他各種御敵之策了。
相對最優的解決辦法是修改注冊和登錄方式,只允許通過微信掃碼以及gitee/github等SSO單點登錄方式,不過這需要額外功能開發,也就是要另外付費,先作為備選方案吧,你懂得的。
在管理后臺反復查看后,就試著修改驗證方式,把原來的的“英文圖片驗證碼”修改為“位圖驗證碼”,肉眼看起來識別難度高一丟丟,不過事實證明,對于黑產來說,這不是事,應該是有方法可以直接破解的,因為它們的請求量并沒明顯下降。
在Discuz管理后臺修改驗證碼設置:
![]()
修改前后驗證碼圖片對比:
攻防階段4:升級驗證方式再次研究Discuz系統管理后臺,發現它的驗證方式,除了驗證碼,還支持提問時互動驗證,默認支持100以內的加減法問題交互驗證。
在管理后臺啟用該功能:
![]()
啟用后效果如下圖所示:
![]()
出人意料的是,啟用該功能后,防護效果非常好,幾乎所有的惡意請求都失效了,雖然還能發起注冊接口請求,但已經無法正確識別互動問題驗證碼,也就沒辦法再把驗證短信額度給刷爆了。完美!
值得表揚的是,該功能還支持自定義互動問題,這就給了我們極大發揮空間,我干脆把部分GreatSQL GCP認證考試題作為互動問題加進來。這樣一來,不但可以防范黑勢力刷接口,還可以讓正常的社區用戶順便當做GCP考試練習,一舉多得。
美中不足的時,這個功能只支持針對 新用戶注冊、發帖、修改密碼這三個動作生效,不支持 用戶登錄(尤其是短信登錄)、忘記密碼這兩個動作,還不能全面防住,還需要進一步想辦法。
![]()
經過一番艱難的摸索測試,最終發現只需要對Discuz源碼中的模板文件做非常小的修改即可實現。
1、修改模板文件 common/seccheck.htm,刪除原文件中第5、8行的條件判斷
1 {eval
2 $sechash = !isset($sechash) ? 'S'.($_G['inajax'] ? 'A'.random(3) : '').$_G['sid'] : $sechash.random(3);
3 $sectpl = str_replace("'", "\'", $sectpl);
4 }
5 <!--{if $secqaacheck}-->
6 <span id="secqaa_q$sechash"></span>
7 <script type="text/javascript" reload="1">updatesecqaa('q$sechash', '$sectpl', '{$_G[basescript]}::{CURMODULE}');</script>
8 <!--{/if}-->
9 <!--{if $seccodecheck}-->
10 <span id="seccode_c$sechash"></span>
11 <script type="text/javascript" reload="1">updateseccode('c$sechash', '$sectpl', '{$_G[basescript]}::{CURMODULE}');</script>
12 <!--{/if}-->
也就是,將上述原文件修改為
1 {eval
2 $sechash = !isset($sechash) ? 'S'.($_G['inajax'] ? 'A'.random(3) : '').$_G['sid'] : $sechash.random(3);
3 $sectpl = str_replace("'", "\'", $sectpl);
4 }
6 <span id="secqaa_q$sechash"></span>
7 <script type="text/javascript" reload="1">updatesecqaa('q$sechash', '$sectpl', '{$_G[basescript]}::{CURMODULE}');</script>
9 <!--{if $seccodecheck}-->
10 <span id="seccode_c$sechash"></span>
11 <script type="text/javascript" reload="1">updateseccode('c$sechash', '$sectpl', '{$_G[basescript]}::{CURMODULE}');</script>
12 <!--{/if}-->
上述改動的作用是使得 用戶登錄功能也能同時啟用兩種驗證方式。
![]()
2、修改模板文件 member/login.htm,在第140行附近插入下面的代碼(這里直接展示git diff的結果)
--- a/member/login.htm
+++ b/member/login.htm
@@ -137,6 +137,11 @@
<div class="layui-form-item">
<input type="text" name="phone" lay-verify="required|phone" autocomplete="off" placeholder="手機號" lay-reqtext="請填寫手機號" class="layui-input phone">
</div>
+ <!--{if $secqaacheck || $seccodecheck}-->
+ <!--{block sectpl}--><div class="layui-form-item secode"><i class="layui-hide"><sec>:</i><sec><i class="img_box"><sec></i></div><!--{/block}-->
+ <!--{subtemplate common/seccheck}-->
+ <!--{/if}-->
+ <div class="layui-form-item">^M
<div class="layui-form-item">
上述改動的作用是使得 忘記密碼功能也能同時啟用兩種驗證方式。
![]()
至此,用戶注冊、用戶登錄、忘記密碼 等多處需要用到短信驗證碼的入口,均已同時啟用兩種驗證方式。
問題暫時得以解決,接下來要繼續關注黑勢力還有什么新的小動作了。
以上,全文完。
如果對你有幫助的話,還請幫忙動動可愛的小手點贊、轉發。