返回列表 发帖
查看: 129|回复: 3

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

1

主题

2

回帖

3

积分

初学乍练

贡献
0 点
金币
0 个
发表于 昨天 09:24 | 查看全部 |阅读模式

无私分享,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 <!--{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">

上述改动的作用是使得 忘记密码功能也能同时启用两种验证方式。

至此,用户注册、用户登录、忘记密码 等多处需要用到短信验证码的入口,均已同时启用两种验证方式。

问题暂时得以解决,接下来要继续关注黑势力还有什么新的小动作了。

以上,全文完。

如果对你有帮助的话,还请帮忙动动可爱的小手点赞、转发。




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

使用道具 举报

1

主题

2

回帖

3

积分

初学乍练

贡献
0 点
金币
0 个
 楼主| 发表于 昨天 10:28 | 查看全部
管理后台中,“验证问答设置”功能,原先代码限制了最多只能显示10个问题,当添加超过10个后,会自动导致100以内加减法功能失效,只需修改一处代码即可解决:

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

  1. --- a/source/admincp/admincp_setting.php
  2. +++ b/source/admincp/admincp_setting.php
  3. @@ -1793,7 +1793,9 @@ EOT;
  4.                 showsubtitle(array('', 'setting_sec_secqaa_question', 'setting_sec_secqaa_answer'));

  5.                 $qaaext = array();
  6. -               foreach(C::t('common_secquestion')->fetch_all($start_limit, 10) as $item) {
  7. +               //验证问题显式最多支持30个(原先是10个)
  8. +               //同时解决了原先超过10个问题后,100以内计算被自动禁用的bug
  9. +               foreach(C::t('common_secquestion')->fetch_all($start_limit, 30) as $item) {
复制代码
回复

使用道具 举报

4

主题

37

回帖

45

积分

初学乍练

贡献
0 点
金币
0 个
发表于 昨天 11:58 | 查看全部
学习一下~
回复

使用道具 举报

1

主题

2

回帖

3

积分

初学乍练

贡献
0 点
金币
0 个
 楼主| 发表于 昨天 12:51 | 查看全部
叶金荣 发表于 2025-8-20 10:28
管理后台中,“验证问答设置”功能,原先代码限制了最多只能显示10个问题,当添加超过10个后,会自动导致10 ...

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

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

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

  1. --- a/source/admincp/admincp_setting.php
  2. +++ b/source/admincp/admincp_setting.php
  3. @@ -1793,7 +1793,10 @@ EOT;
  4.                 showsubtitle(array('', 'setting_sec_secqaa_question', 'setting_sec_secqaa_answer'));

  5.                 $qaaext = array();
  6. -               foreach(C::t('common_secquestion')->fetch_all($start_limit, 10) as $item) {
  7. +               foreach(C::t('common_secquestion')->fetch_all() as $item) {


  8. --- a/source/class/helper/helper_seccheck.php
  9. +++ b/source/class/helper/helper_seccheck.php
  10. @@ -107,7 +107,9 @@ class helper_seccheck {
  11.         public static function make_secqaa() {
  12.                 global $_G;
  13.                 loadcache('secqaa');
  14. -               $secqaakey = max(1, random(1, 1));
  15. +               $secqaakey = rand(1, count($_G['cache']['secqaa']));


  16. --- a/source/function/cache/cache_secqaa.php
  17. +++ b/source/function/cache/cache_secqaa.php
  18. @@ -15,19 +15,25 @@ function build_cache_secqaa() {
  19. -       foreach(C::t('common_secquestion')->fetch_all($start_limit, 10) as $secqaa) {
  20. +       foreach(C::t('common_secquestion')->fetch_all() as $secqaa) {
  21.                 if(!$secqaa['type'])  {
  22.                         $secqaa['answer'] = md5($secqaa['answer']);
  23.                 }
  24.                 $data[$i] = $secqaa;
  25.                 $i++;
  26.         }
  27. -       while(($secqaas = count($data)) < 9) {
  28. +       while(($secqaas = count($data)) < $secqaanum) {
复制代码


欢迎关注我的微信公众号:老叶茶馆
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

  • 关注公众号
  • 有偿服务微信
  • 有偿服务QQ

手机版|小黑屋|Discuz! 官方交流社区 ( 皖ICP备16010102号 |皖公网安备34010302002376号 )|网站地图|star

GMT+8, 2025-8-21 06:30 , Processed in 0.044225 second(s), 10 queries , Redis On.

Powered by Discuz! W1.0 Licensed

Copyright © 2001-2025 Discuz! Team.

关灯 在本版发帖
有偿服务QQ
有偿服务微信
返回顶部
快速回复 返回顶部 返回列表