Discuz!小编 发表于 2025-8-20 18:40:12

无私分享,discuz仅需微小改动即可防范黑客暴力刷接口 New

无私分享,discuz仅需微小改动即可防范黑客暴力刷接口

引言天降飞锅攻防阶段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
6               
7               updatesecqaa('q$sechash', '$sectpl', '{$_G}::{CURMODULE}');
8
9
10               
11               updateseccode('c$sechash', '$sectpl', '{$_G}::{CURMODULE}');
12
也就是,将上述原文件修改为
1 {eval
2         $sechash = !isset($sechash) ? 'S'.($_G['inajax'] ? 'A'.random(3) : '').$_G['sid'] : $sechash.random(3);
3         $sectpl = str_replace("'", "\'", $sectpl);
4 }
6               
7               updatesecqaa('q$sechash', '$sectpl', '{$_G}::{CURMODULE}');
9
10               
11               updateseccode('c$sechash', '$sectpl', '{$_G}::{CURMODULE}');
12
上述改动的作用是使得 用户登录功能也能同时启用两种验证方式。
2、修改模板文件 member/login.htm,在第140行附近插入下面的代码(这里直接展示git diff的结果)
--- a/member/login.htm
+++ b/member/login.htm
@@ -137,6 +137,11 @@
                                 
                                    
                                 
+                              
+                               :
+                              
+                              
+                              ^M
                                 
上述改动的作用是使得 忘记密码功能也能同时启用两种验证方式。
至此,用户注册、用户登录、忘记密码 等多处需要用到短信验证码的入口,均已同时启用两种验证方式。
问题暂时得以解决,接下来要继续关注黑势力还有什么新的小动作了。
以上,全文完。
如果对你有帮助的话,还请帮忙动动可爱的小手点赞、转发。



原文链接:https://mp.weixin.qq.com/s/PNIwKHNKC1IxOFyOyHXGPA
我知道答案 回答被采纳将会获得1 贡献 已有3人回答

KarlMock59 发表于 2025-8-20 18:40:19

管理后台中,“验证问答设置”功能,原先代码限制了最多只能显示10个问题,当添加超过10个后,会自动导致100以内加减法功能失效,只需修改一处代码即可解决:

修改代码文件:source/admincp/admincp_setting.php 约1796行附近,git diff结果如下

--- a/source/admincp/admincp_setting.php+++ b/source/admincp/admincp_setting.php@@ -1793,7 +1793,9 @@ EOT;                showsubtitle(array('', 'setting_sec_secqaa_question', 'setting_sec_secqaa_answer'));                $qaaext = array();-               foreach(C::t('common_secquestion')->fetch_all($start_limit, 10) as $item) {+               //验证问题显式最多支持30个(原先是10个)+               //同时解决了原先超过10个问题后,100以内计算被自动禁用的bug+               foreach(C::t('common_secquestion')->fetch_all($start_limit, 30) as $item) {

瑾瑜 发表于 2025-8-20 18:40:58

学习一下~

海军大都督 发表于 2025-8-20 18:41:24

这里其实还可以修改为下面这样,实现问答数量无上限

foreach(C::t('common_secquestion')->fetch_all() as $item) {

此外,其他代码文件也要同步修改,完整git diff如下

--- a/source/admincp/admincp_setting.php+++ b/source/admincp/admincp_setting.php@@ -1793,7 +1793,10 @@ EOT;                showsubtitle(array('', 'setting_sec_secqaa_question', 'setting_sec_secqaa_answer'));                $qaaext = array();-               foreach(C::t('common_secquestion')->fetch_all($start_limit, 10) as $item) {+               foreach(C::t('common_secquestion')->fetch_all() as $item) {--- a/source/class/helper/helper_seccheck.php+++ b/source/class/helper/helper_seccheck.php@@ -107,7 +107,9 @@ class helper_seccheck {      public static function make_secqaa() {                global $_G;                loadcache('secqaa');-               $secqaakey = max(1, random(1, 1));+               $secqaakey = rand(1, count($_G['cache']['secqaa']));--- a/source/function/cache/cache_secqaa.php+++ b/source/function/cache/cache_secqaa.php@@ -15,19 +15,25 @@ function build_cache_secqaa() {-       foreach(C::t('common_secquestion')->fetch_all($start_limit, 10) as $secqaa) {+       foreach(C::t('common_secquestion')->fetch_all() as $secqaa) {                if(!$secqaa['type']){                        $secqaa['answer'] = md5($secqaa['answer']);                }                $data[$i] = $secqaa;                $i++;      }-       while(($secqaas = count($data)) < 9) {+       while(($secqaas = count($data)) < $secqaanum) {

欢迎关注我的微信公众号:老叶茶馆
页: [1]
查看完整版本: 无私分享,discuz仅需微小改动即可防范黑客暴力刷接口 New