<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      做了一個概率小游戲,沒想到服務器被打爆被攻擊了!原因竟然是他?真沒想到...

      1. 前言

      事情是這樣的,上個月在刷知乎的過程中,發現了以下幾個有趣的問題。

      1. 每毫秒給你1個億,代價是你每秒被動觸發一次1億分之一的死亡率,你愿意嗎?
      2. “100%概率獲得200萬”和“99%概率獲得2個億”,你選哪個?

      作為程序員,看著這種概率與決策,感覺非常有趣,有時候常在想,能不能用程序模擬一下,選擇哪個選擇,我最終取勝的概率最大呢?

      于是就有了我的服務器被打爆攻擊的事情了,欲哭無淚。讓我給大家講講我怎么和攻擊者在線上斗智斗勇的。

      先給大家簡單看看這個游戲的效果。

      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%的惡意請求了,你還有更好的防攻擊手段么,歡迎評論區留言討論。

      posted @ 2025-10-21 21:19  程序員博博  閱讀(1201)  評論(19)    收藏  舉報
      主站蜘蛛池模板: 97精品久久久大香线焦| 人人人澡人人肉久久精品| 无码熟妇人妻AV影音先锋| 人人做人人妻人人精| 永久免费AV无码网站YY| 亚洲精品一区久久久久一品av| 亚洲日韩乱码一区二区三区四区 | 日区中文字幕一区二区| 伊人久久大香线蕉成人| 麻豆精品久久精品色综合| 风韵丰满妇啪啪区老老熟女杏吧| 一道本AV免费不卡播放| 国产偷国产偷亚洲清高网站| 国产高清精品一区二区三区| 性奴sm虐辱暴力视频网站 | 国产精品天天看天天狠| 亚洲一级特黄大片在线播放| 日韩精品人妻av一区二区三区| 日韩高清亚洲日韩精品一区二区| 久久精品国产清自在天天线| 欧美成人一卡二卡三卡四卡| 国产国产午夜福利视频| 精品无码国产一区二区三区AV| 国产高清精品在线91| 在线观看的网站| 日韩乱码卡一卡2卡三卡四| 久久亚洲精品情侣| 亚洲伊人五月丁香激情| 国产精品老熟女乱一区二区| 国产亚洲人成网站在线观看| 漂亮的保姆hd完整版免费韩国| 丹江口市| 国产精品中文字幕综合| 亚洲精品久荜中文字幕| 99久久国产综合精品女同| 成在线人永久免费视频播放| 亚洲精品久久久久久久久久吃药 | 丰满少妇被猛烈进出69影院| 午夜三级成人在线观看| 亚洲国产精品毛片av不卡在线| 久久久亚洲欧洲日产国码aⅴ|