做了一个概率小游戏,没想到服务器被打爆被攻击了!原因竟然是他?真没想到...

news/2025/10/21 21:23:15/文章来源:https://www.cnblogs.com/wenbochang/p/19156433

1. 前言

事情是这样的,上个月在刷知乎的过程中,发现了以下几个有趣的问题。

  1. 《每毫秒给你1个亿,代价是你每秒被动触发一次1亿分之一的死亡率,你愿意吗?》
  2. 《“100%概率获得200万”和“99%概率获得2个亿”,你选哪个?》

作为程序员,看着这种概率与决策,感觉非常有趣,有时候常在想,能不能用程序模拟一下,选择哪个选择,我最终取胜的概率最大呢?

于是就有了我的服务器被打爆攻击的事情了,欲哭无泪。让我给大家讲讲我怎么和攻击者在线上斗智斗勇的。

先给大家简单看看这个游戏的效果。

  • 体验地址
  • github源码地址

1

2

3

2. 事件过程

2.1 事情起因

这个小游戏是非常简单的,完全看个人运气,有些人可能运气就能抽中比较长的时间,有些人可能运气非常差抽中时间很短。因此在10.6号左右为了增加游戏的趣味性,我就上线了排行榜机制!!!

万万没想到,大家的‘斗志’实在太高了,有些人通过爬取我的后端接口,给自己一个非常夸张的数据,让自己排第一名,也就是他也是一名程序员,然后通过绕过前端的手段,直接给我后端放进夸张的数据。当时用户名满天飞,什么‘xxx一日游’, ‘我是第一名’,‘比不过我吧’等等名称满天飞。

作为资深程序员,我能忍?平时的八股文派上用场了。

2.2 第一回合 - 防重放

  1. 先做一些简单的数据校验,比如用户名的长度,数据的范围等等,非常的基础
  2. 对前端的UA,REFER等做一些基础的校验
  3. 加一个token校验,也就是前端要通过某些规则生成一个token传给后端,后端在根据这个规则来校验这个token是否合法,如果不合法,则直接拒绝,说明用户是非法请求,代码如下
public static boolean extractSecret(StringRedisTemplate redisService, String timestamp, String token, TreeMap<String, String> map) {if (StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(token)) {return false;}long ts = NumberUtils.toLong(timestamp, 0);long now = System.currentTimeMillis();if ((now - ts) > SecretUtils.NONCE_DURATION) {return false;}StringBuilder sb = new StringBuilder();map.put("salt", SALT);for (Map.Entry<String, String> entry : map.entrySet()) {String key = entry.getKey();String value = entry.getValue();if (sb.length() > 0) {sb.append("&");}sb.append(key).append("=").append(value);}String targetToken = DigestUtils.md5DigestAsHex(sb.toString().getBytes());if (!token.equals(targetToken)) {return false;}String s = redisService.opsForValue().get(timestamp);if (StringUtils.isNotEmpty(s)) {return false;} else {redisService.opsForValue().set(timestamp, timestamp, NONCE_DURATION, TimeUnit.MILLISECONDS);}return true;
}	
  • 首先前端会生成一个时间戳,然后将时间戳+所有请求的参数,通过字典序进行排序
  • 排序之后,生成一个类似于 a=1&b=2&c=3 的字符串,然后使用md5生成一个token传给后端
  • 后端接收以后,首先判断时间戳是否过了很久,保证不是手动生成的
  • 其他根据规则自己也生成一个token,比较两个token是否相同,为了保险起见,一般双方会约定一个salt盐这个一个参数,起到混淆视听的作用
  • 最后后端会把这个时间戳、token放进redis,保证一个token只能使用一次,使用了之后就不能再次使用了

通过这个防重放的防御,大部分水货程序员就会拦截在了门外

2.3 第二回合 - 前端js加盐混淆视听

过了一段时间,攻击者竟然破解了我的加密手段,针对token的生成规则,有些大佬可以通过f12非常方便的看到前端的代码,然后获取到规则,然后利用代码进行攻击,于是乎

  • 针对前端盐,我进行了混淆视听,举一个例子
  • 比如我的salt=abc 现在我换成如下代码,你还能看得懂么?
// 签名生成核心
const _0xsig = {// 混淆配置矩阵(多层编码)_0xa1: [0x64,0x61,0x5f,0x6c,0x61,0x6f,0x5f,0x62,0x69,0x65,0x5f,0x7a,0x61,0x69,0x5f,0x73,0x68,0x75,0x61,0x5f,0x6a,0x69,0x65,0x5f,0x6b,0x6f,0x75,0x5f,0x6c,0x65],_0xa2: [0x77,0x6f,0x5f,0x64,0x65,0x5f,0x6a,0x69,0x65,0x5f,0x6b,0x6f,0x75,0x5f,0x62,0x61,0x6f,0x5f,0x6c,0x65],_0xa3: [0x73,0x68,0x6f,0x75,0x5f,0x78,0x69,0x61,0x5f,0x6c,0x69,0x75,0x5f,0x71,0x69,0x6e,0x67],_0xb1: [115,97,108,116],_0xb2: [115,97,108,116,95,118,50],_0xb3: [115,108,97,116,95,118,51],_0xc1: 0x1a2b,_0xc2: 0x3c4d,_0xc3: 0x5e6f,// 生成签名_0xgen(_0xdata) {const _0xt = Date.now()[_0x3c4d(2)]();const _0xp = { ..._0xdata, timestamp: _0xt };// 提取密钥和值const _0xkeys = this._0xextK();const _0xvals = this._0xextV();// 构建参数对象const _0xall = { ..._0xp };for (let _0xi = 0; _0xi < _0xkeys.length; _0xi++) {_0xall[_0xkeys[_0xi]] = _0xvals[_0xi];}// 排序并拼接const _0xks = Object.keys(_0xall).sort();const _0xstr = _0xks.map(_0xk => `${_0xk}=${_0xall[_0xk]}`).join('&');const _0xtk = _0xmd5(_0xstr);return { ..._0xp, token: _0xtk };},// 创建签名create(_0xparams) {return this._0xgen(_0xparams);}
};

大概率你看的很懵逼,这种方式一般人几乎破解不了,除非通过AI进行分析

  • 我通过对字符a等进行16进制,然后通过增加多盐的方式,增加攻击者的攻击成本
  • 像网易云、知乎等都是采用这种方法

2.4 第三回合 - IP限流

过了一段时间,攻击者又又又破解了,并且好像非常生气,开始恶意请求我的接口了,通过脚本一直刷我的接口,让我的服务器直接挂掉,当时我的服务器承受不住这么高的流量,就直接重启了,重启后,又被打爆,我当时真的特么无语了,对这种人。

而且我的服务器的流量一直被刷,都快刷欠费了,真的不能忍,我都想直接把应用给下线了。当然作为资深程序员怎么能忍受

  • 针对高频IP地址进行限流,比如1s内请求10s,10s内请求100次的ip,肯定不是一个正常用户,是一个非法用户,直接封禁
  • 加密代码不再开源(之前一直开源,感觉攻击者偷偷看我的commit,我在明他在暗,怎么玩),直接修改salt参数,并且启用多重盐,让你怎么破解,具体限流代码如下
private boolean checkRateLimit(String ip, String uri, HttpServletResponse response) throws IOException {// 1. 检查是否在黑名单中String blacklistKey = BLACKLIST_KEY_PREFIX + ip;String blacklistValue = stringRedisTemplate.opsForValue().get(blacklistKey);if (blacklistValue != null) {Long ttl = stringRedisTemplate.getExpire(blacklistKey, TimeUnit.SECONDS);log.error("IP黑名单拦截 - IP={}, URI={}, 剩余时长={}秒", ip, uri, ttl);writeErrorResponse(response, "签名验证失败");return false;}// 2. 检查访问频率String rateLimitKey = RATE_LIMIT_KEY_PREFIX + ip;String countStr = stringRedisTemplate.opsForValue().get(rateLimitKey);long count = 0;if (countStr != null) {count = Long.parseLong(countStr);}// 3. 递增计数Long newCount = stringRedisTemplate.opsForValue().increment(rateLimitKey, 1);// 4. 如果是第一次访问,设置过期时间if (count == 0) {stringRedisTemplate.expire(rateLimitKey, RATE_LIMIT_WINDOW, TimeUnit.SECONDS);log.info("IP限流 - IP={}, {}秒内第1次请求{}", ip, RATE_LIMIT_WINDOW, uri);return true;}// 5. 检查是否超过限制if (newCount > RATE_LIMIT_MAX_COUNT) {// 超过限制,加入黑名单stringRedisTemplate.opsForValue().set(blacklistKey, String.valueOf(newCount), BLACKLIST_DURATION, TimeUnit.SECONDS);log.error("IP限流触发 - IP={}, {}秒内请求{}次(限制{}次),已拉黑{}秒, URI={}", ip, RATE_LIMIT_WINDOW, newCount, RATE_LIMIT_MAX_COUNT, BLACKLIST_DURATION, uri);writeErrorResponse(response, "签名验证失败");return false;}// 6. 正常通过,记录日志Long ttl = stringRedisTemplate.getExpire(rateLimitKey, TimeUnit.SECONDS);log.info("IP限流 - IP={}, {}秒内第{}次请求{}(限制{}次),剩余{}秒", ip, RATE_LIMIT_WINDOW, newCount, uri, RATE_LIMIT_MAX_COUNT, ttl);return true;
}

大概的意思就是请求多少秒内请求超过多少次,我就认为你不是一个正常用户,直接封禁即可。

2.5 第四回合

别看了,木有了,又又又又被破解了,我实在没招了,看看评论区的大佬们有没有什么好的办法支支招

3. 最后

通过这个例子,我们发现攻击者与我们一来一回,真所谓是道高一丈,魔高一丈。攻击者力量比较大,毕竟人多。
我们简单总结一下,我们大概有以下技术手段可以防止攻击者的攻击

  1. 后端的一些基础数据校验,比如针对用户名,存活时间,浏览器UA等等
  2. 防重放token校验,通过和前端约定一些规则,通过规则来生成token,防止恶意请求
  3. 在token校验的基础上,我们使用了salt盐,并且对盐的生成进行了混淆,导致攻击者的攻击成本非常的高
  4. 针对大量脚本刷接口的行为,我们利用redis进行了ip限流,如果在某个时间内请求超过了某个次数,直接禁止请求

基本上通过以上技术手段,我们可以拦截99%的恶意请求了,你还有更好的防攻击手段么,欢迎评论区留言讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/942668.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

接下来的目标

本学期目标: 1.学完redis黑马点评,掌握关于redis,分布式锁,秒杀等知识点 2.八股文在javaguide上观看一遍 3.算法题hot100刷第一遍 4.看完《深入理解jvm虚拟机》目前已看完历史即第一章,准备编译openjdk12; 5.写完…

敬启,致那时的我

题面 题目描述 实乃理给你两个整数 \(S, k\),你需要帮她求出以下式子的值对 \(1,000,000,007\) 取模的结果: \[\sum_{X = 0}^S [\mathrm{popc}(X) = k]F(X) \]其中 \(F\) 为斐波那契数列,即 \(F(0) = F(1) = 1, F(n…

阿里云对象存储OSS之Java - Soul

阿里云对象存储OSS 介绍阿里云对象存储OSS是一款非常强大的云存储服务——提供的海量、安全、低成本、高持久性的对象存储服务,通过RESTful API提供HTTP接口。 核心特性:无限扩展:存储空间和文件数量无上限。 多存储…

清楚标签默认样式,内容溢出盒子时的处理

所有标签都有默认样式 scroll:无论是否一处都显示水平和竖直的滚动条, auto:只有当水平或者竖直方向移出时才会显示

Solidity合约继承场景下的构造函数执行顺序

Solidity合约继承场景下的构造函数执行顺序“从远到近,从左到右” 举例,TetherToken有如下继承关系: TetherToken is Pausable, StandardToken, BlackList多重继承时,"从左到右",先初始化 Pausable,再…

后量子密码学技术与标准化进程解析

本文深入探讨后量子密码学技术发展现状,涵盖NIST标准化进程、密码算法创新及实际部署方案。重点分析SPHINCS+签名框架、混合密钥交换机制在TLS协议中的实现,以及云计算环境下的技术迁移路径。为后量子密码学未来做好…

用 大模型 和 Gradio 构建一个 AI 反向词典

卡在嘴边的词?用 MindSpore 和 Gradio 构建一个 AI 反向词典 你是否曾有过这样的经历:一个概念、一种情绪或一个场景在脑海中无比清晰,但就是想不起那个最贴切的词来形容它?这种“话在嘴边说不出”的窘境,是我们在…

1279. 红绿灯路口

1279. 红绿灯路口 题目描述这是两条路的交叉路口。第一条路是 A 路,车辆可沿 1 号方向由北向南行驶,也可沿 2 号方向由南向北行驶。第二条路是 B 路,车辆可沿 3 号方向由西向东行驶,也可沿 4 号方向由东向西行驶。…

反数字化:线下活动也能年赚百万

Jonathan Courtney在2025年靠线下高端小众活动赚了100万美元。以下是他实操过的11步清单👇1️⃣. 选定方向:反潮流,做"反AI反数字化",专注线下体验。1分钟就能决定,大胆出手,不卷线上。2️⃣. 明确细…

python概念详解

1.is和==有什么区别? 在 Python 中,is和都用于比较,但它们的比较对象和逻辑完全不同,核心区别在于:is比较的是 “对象身份”(是否为同一个对象),比较的是 “对象值”(是否相等)。is:比较的是两个对象的id值…

JAVA基础理解

1.封装 封装是面向对象的三大特征之一,意思为隐藏对象的属性和实现细节,Java中的封装就是根据访问控制修饰符实现的public(公共的):对外公开,所有类都可以访问 protected(受保护的): 只能在同一个包中的类或子…

用户消费行为数据分析(随笔)

需求分析(流程) 1、数据收集 项目的第一步是收集大量的用户消费数据。 2、数据清洗与预处理 收集到的数据可能存在错误、缺失或重复项,需要进行数据清洗和预处理,确保数据的准确性和完整性,使其适合后续的工作。 …

sqlserver 主要的日期函数及用法示例

SQL Server 主要日期函数及用法示例 1. 获取当前日期和时间sqlSELECT GETDATE() AS CurrentDateTime, -- 当前日期时间GETUTCDATE() AS CurrentUTCDateTime, -- 当前UTC时间SYSDATETIME() AS SystemDate…

ICPC2022沈阳 游记(VP)

中期艰难挂机,最后勉强四题仅有铜中。省流 中期艰难挂机,最后勉强四题仅有铜中。10.21 内含剧透,请vp后再来。 不是题解!!!!!!! 赛前 点外卖没给一次性筷子,飞奔到食堂获取圣遗物然后 \(4min22s\) 速通结块…

大数据分析基础及应用案例:第四周学习报告——线性回归模型

时间过得很快,大数据分析学习已经进入第四周。这一周,我们聚焦于线性回归模型—— 这个看似简单却贯穿数据分析领域的核心算法。从理论公式推导到实际案例落地,我不仅掌握了模型的基本逻辑,更体会到 “用数据解释规…

「LG7446-rfplca」题解

题解记录P7446 [Ynoi2007] rfplca sol 考虑如何找 LCA,通常来说我们会使用倍增,然而这道题带修,因此倍增不可实现。 考虑对序列分块,每个点维护其父亲以及其最近的不与其在同一块中的祖先,散块重构是简单的,但貌…

图论刷题记录

P8186 [USACO22FEB] Redistributing Gifts SFloyd 传递闭包模板。 首先对于每只奶牛,先看它和那些比在它目前手中礼物要珍贵的礼物的主人能否交换,然后做一遍传递闭包,最后对于每只奶牛直接找排名最靠前并且能与自己…

「LG6596-How Many of Them」题解

题解记录P6596 How Many of Them sol 首先发现 \(n\) 特别小(事实上不如题中给出的这么小。。),于是考虑枚举割边数量。 这么做的一个重要根据是存在如下结论:对于一个 \(n\) 个点,已有 \(k\) 个联通块的图,记第…

骗我呢

\(\mathbf{Part. 1}\) 从右往左考虑肯定没啥前途,我们考虑从上往下扫行。对于每一行,它上面的元素肯定都是单调递增的,又知道元素的值域在 \(0\) 到 \(m\),而一行总共有 \(m\) 个数,因此每行可以被表示为 \(0\) 到…

手搓文件管理系统(持续开发中)

#include<bits/stdc++.h> using namespace std;/*** command:* cd [dirname]* to shift to a name_given dir* mkdir [dirname]* create a new dir under current dir* ls [dirname]* list…