惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Security Latest
Security Latest
C
CXSECURITY Database RSS Feed - CXSecurity.com
AI
AI
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
P
Palo Alto Networks Blog
Spread Privacy
Spread Privacy
Project Zero
Project Zero
Google Online Security Blog
Google Online Security Blog
The Cloudflare Blog
L
LangChain Blog
T
Tenable Blog
GbyAI
GbyAI
C
Cybersecurity and Infrastructure Security Agency CISA
大猫的无限游戏
大猫的无限游戏
Last Week in AI
Last Week in AI
量子位
Cloudbric
Cloudbric
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
S
Secure Thoughts
P
Privacy & Cybersecurity Law Blog
S
Securelist
S
Schneier on Security
F
Full Disclosure
Engineering at Meta
Engineering at Meta
A
Arctic Wolf
Microsoft Security Blog
Microsoft Security Blog
Apple Machine Learning Research
Apple Machine Learning Research
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
爱范儿
爱范儿
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
P
Privacy International News Feed
宝玉的分享
宝玉的分享
A
About on SuperTechFans
博客园 - 【当耐特】
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
Webroot Blog
Webroot Blog
Google DeepMind News
Google DeepMind News
H
Heimdal Security Blog
Y
Y Combinator Blog
月光博客
月光博客
H
Hacker News: Front Page
MongoDB | Blog
MongoDB | Blog
C
Check Point Blog
TaoSecurity Blog
TaoSecurity Blog
腾讯CDC
N
Netflix TechBlog - Medium

博客园 - 郎中令

锦衣夜行,AI乐园 Redis实战:用缓存为数据库减负(二) Redis初体验: 搭建与简单应用(一) .Net Core的SwaggerUI接口分类+固定路由 真实性编码:配置NLog日志+Seq分布式 防御性编码:手搓NLog日志+Seq分布式 对接Java所谓的DES加解密 人大金仓数据库转换 人大金仓踩坑指南 RSA加解密笔记 HTTPS请求笔记- SSL安全通道验证问题 .NetCore打包部署(DLL) 字典映射处理 SM4算法快速预览与Framework4.5版本对接 vite运行打包前端-部署Linux 记一次线上数据库异常的协助排查 降本增笑:记一次首页并发白屏优化过程 solr 基础介绍以及踩坑日记 动态展示缩放背景图
简易首页防暴力-字典计时器
郎中令 · 2024-05-16 · via 博客园 - 郎中令

       有时候首页需要限制下相同账号的错误登录次数,防止暴力破解,实际而言,还是有一点点作用,虽然并不是很大,一定层度上也能扼杀一番,主要是调整起来方便,对于老旧系统改造起来比较快,核心是字典,一个记录失败次数,一个记录账号解锁的时间,在账号登录时先去字典里面校验,不用频繁的请求数据库.  需要注意的是,这个字典要设置为全局。否则切换客服端就会失效.

        //次数字典
        private static Dictionary<string, int> errorCounts = new Dictionary<string, int>();
        //时间字典
        private static Dictionary<string, DateTime> lockoutTimes = new Dictionary<string, DateTime>();

字典设置完毕,接下来就是在登录的时机点上校验次数,基本思路是,每个登录进来的账号无论密码,先加到字典中,设置一个初始时间,后边后续统一判断

        public ActionResult ICCLogin(string userName, string password)
        {
            int result;
            DateTime dateTime;
            //先加时间字典
            if (!errorCounts.TryGetValue(userName, out result))
            {
                errorCounts[userName] = 0;
                lockoutTimes[userName] = DateTime.MinValue;
            }
            //判断次数字典
            if (IsLockedOut(userName, out dateTime))
            {
                // 计算两个日期时间之间的时间间隔
                TimeSpan timeDifference = dateTime.Subtract(DateTime.Now);
                // 计算总分钟数并向上取整
                int totalMinutes = (int)Math.Ceiling(timeDifference.TotalMinutes);
                // 如果向上取整后的分钟数小于1,设为30
                if (totalMinutes == 1)
                {
                    totalMinutes = 30;
                }
                string suf = totalMinutes == 30 ? "s" : "分钟";
                LibExceptionManagent.ThrowErr(string.Format("验证失败次数过多,账户已被锁定,{0}{1}后重试", totalMinutes, suf));
                return View();
            }

次数字典方法

        private bool IsLockedAccount(string username, out DateTime dateTime)
        {
            //先加次数字典
            if (!lockoutTimes.ContainsKey(username))
            {
                lockoutTimes[username] = DateTime.MinValue;
            }
            //获取当前账号的可放开时间
            dateTime = lockoutTimes[username];
            return lockoutTimes[username] > DateTime.Now;
        }

贸然看去,貌似没啥子问题,实际测试中可能存在一个隐藏问题,比如5分钟内错误3次,超过5分钟后,再次输错,应该从0开始计算,而不是变成第四次

        private bool IsLockedOut(string username, out DateTime dateTime)
        {
            //先加次数字典
            if (!lockoutTimes.ContainsKey(username))
            {
                lockoutTimes[username] = DateTime.MinValue;
            }
            //获取当前账号的可放开时间
            int times = 0;
            dateTime = lockoutTimes[username];
            TimeSpan timeDifference = DateTime.Now.Subtract(dateTime);
            int temp = (int)Math.Ceiling(timeDifference.TotalMinutes);
            bool bol = temp>=0 && lockoutTimes[username]!= DateTime.MinValue;
            if (bol && errorCounts.TryGetValue(username, out times))
            {
                errorCounts[username] = 0;
            }
            return bol;
        }

实际上还缺少一个归零的操作,到达账号解封时间后, 需要置空错误次数,否则就会无限循环,5分钟结束后又从头开始

        private bool IsLockedOut(string username, out DateTime dateTime)
        {
            //先加次数字典
            if (!lockoutTimes.ContainsKey(username))
            {
                lockoutTimes[username] = DateTime.MinValue;
            }

            dateTime = lockoutTimes[username];
            bool bol = lockoutTimes[username] > DateTime.Now;
            //获取当前账号的可放开时间
            if (bol && errorCounts.TryGetValue(username, out _))
            {
                errorCounts[username] = 0;
            }
            return bol;
        }

写到次数,貌似没啥子问题,实际上存在漏洞,没有对时间,次数有完整的管理,使用过程中数据会错乱,重新捋一下逻辑,完整的判断方法如下

        private bool IsLockedOut(string userName, out DateTime dateTime)
        {
            //时间字典初始化
            if (!lockoutTimes.ContainsKey(userName))
            {
                lockoutTimes[userName] = DateTime.MinValue;
            }
            //次数字典初始化
            if (!errorCounts.ContainsKey(userName))
            {
                errorCounts[userName] = 0;
            }
            //判断时间,
            dateTime = lockoutTimes[userName];
            if (dateTime == DateTime.MinValue) 
            {
                return false;  //次数未锁定
            }
            //计算解锁时间,满足则清空次数,重置时间
            if (dateTime <= DateTime.Now)
            {
                //5次满足解锁时间   //不足5次满足间隔5分钟
                if (errorCounts[userName] >= 5 || (DateTime.Now - dateTime).TotalSeconds >= 300)
                {
                    errorCounts[userName] = 0;
                    lockoutTimes[userName] = DateTime.MinValue;
                    return false;
                }
                return false;
            }
            return true;
        }

如此基本上满足次数校验,为了形成一个小小的闭环,全局静态字典需要回收,再写一个定时任务清空字典,避免字典值越来越大

        private static readonly Timer timer = new Timer(ClearDictionary, null, TimeSpan.Zero, TimeSpan.FromMinutes(30));

        private static void ClearDictionary(object state)
        {
            // 在定时器触发时清空字典
            //Dictionary<string, DateTime> keysToRemove = lockoutTimes.Where(pair => pair.Value != DateTime.MinValue && pair.Value <= DateTime.Now);
            foreach (var dic in lockoutTimes)
            {
                if (dic.Value == DateTime.MinValue)
                {
                    continue;
                }
                double doble = (DateTime.Now - dic.Value).TotalSeconds;
                if (doble >= 300)
                {
                    lockoutTimes.Remove(dic.Key);
                    int count;
                    if (errorCounts.TryGetValue(dic.Key, out count))
                    {
                        errorCounts.Remove(dic.Key);
                    }
                }
            }
        }

 失败次数的累加相对比较简单,贴一下代码,不做特殊说明

            else
            {
                lockoutTimes[userName] = DateTime.Now;
                if (!errorCounts.ContainsKey(userName))
                {
                    errorCounts[userName] = 1;
                }
                else
                {
                    errorCounts[userName]++;
                    if (errorCounts[userName] >= 5)
                    {
                        lockoutTimes[userName] = DateTime.Now.AddMinutes(5); // 锁定账户5分钟
                        LibExceptionManagent.ThrowErr("验证失败次数过多,账户已被锁定,5分钟后重试");
                    }
                }
            }
            LibExceptionManagent.ThrowErr("提供的用户名或密码不正确");
            return View();

ok , 搞定