年會抽獎程序的一些總結


起源

2019年年會的到來,當然免不了激動人心的抽獎環節啦,那直接延用上一年的抽獎程序吧,然而Boss希望今年的抽獎程序能夠能讓所有人都參與進來,一起來搶有限獎品,先到先得,而不是站在那里盯着屏幕。

OK,程序內容大概是這樣子,每個人在手機瀏覽器打開抽獎程序界面,系統會隨機給個數字,誰戳屏幕上的圓形最快最准,就能參與抽獎活動,有多少獎品就有多少場battle(僅限手機瀏覽器打開 & 每人僅限獲得一個獎品

重要的是,年會之前得把程序公布出來,讓其他同事想辦法作弊,硬件作弊軟件作弊都允許,我們主要分析軟件作弊並制定對應防御策略,就像是一場CTF,對方是攻擊,我們是防御。

本文有引用他人文章,若有侵權或其它不當,會立即刪除。

開發過程

我跟另一個同事完成這個項目,他負責后端,我負責前端,整體采用React+Nodejs+Express+MySQL的架構,這個項目有實時的需求,因此我們用到了socket.io

首先,我們分析了對方作弊的可能:

  1. 通過抓包分析,成功偽造點擊請求
  2. 找到圓形位置,實現高准確率的點擊
  3. 搞垮其他競賽者的賬號,自己穩步前進
  4. 直接搞掉服務器,修改數據庫

對於作弊3,賬號列表是年會抽獎前10分鍾公布的,所以作弊者沒有充足的准備時間。
對於作弊4,都是自己人,應該不會那么狠吧...

那么,我們主要針對作弊1作弊2做了以下防御:
后端防御

  • 校驗請求參數是否有效
  • 分析正確點擊頻率,超過正常點擊閥值則作懲罰處理
  • 點擊錯誤次數過多則作懲罰處理

前端防御

  • 使用webpack-obfuscator混淆打包js代碼,加大代碼分析難度
  • react-konva畫圓形,防止對手輕易獲取DOM節點並執行點擊事件
  • 圓形的顏色和位置隨機,防按鍵精靈之類的軟件
  • 核心websocket請求采用白盒加密
  • 普通請求采用rsa+aes混合加解密

經過了一個星期的開發和防御策略研究,我們把程序弄出來了,等待年會的到來。

<img style="width:1050px;"src="https://img2018.cnblogs.com/blog/900334/201901/900334-20190127160433727-2083057780.jpg" />

抽獎過程

年會抽獎那天,在大屏幕上面可以看到每個人的點擊數,注意到有個同事的按擊次數值飈得極快,很明顯他作弊成功了,但我想不明白是怎么做到的。於是年會結束后我向他請教,才得知作弊手法。

結果分析

作弊手法如下,非常簡單,在手機瀏覽器的地址欄輸入下面一段代碼,回車即可執行。跟我一樣不知道瀏覽器地址欄可以執行js代碼的童鞋,請看這篇文章瀏覽器地址欄運行JavaScript代碼

javascript:Math.random=function(){return 1;}

由於我在圓形隨機位置和隨機顏色的設置上用到了Math.random這個方法,此時,他的按擊圓形就定死在同一個位置,眾人皆動我獨靜,而且都是正常點擊請求,必須贏呀!

// 修改前
function getRandomNumber(min, max) {
    const range = max - min;
    const random = Math.random();
    const num = min + range * random;
    return num;
}

代碼修補

找到問題所在,很明顯我需要找個隨機函數方法替換Math.random,於是我google了幾篇文章,都提到線性同余生成器(LCG, Linear Congruential Generator),貌似所有運行庫都是采用這種算法生成偽隨機數,我就把getRandomNumber函數改成下面的模樣,問題得到了暫時的解決。

// 修改后
const getRandomNumber = (function() {
    let seed = Date.now();
    function random() {
        //線性同余生成器(LCG, Linear Congruential Generator)
        seed = (seed * 9301 + 49297) % 233280;
        return seed / (233280.0);
    };
    return function rand(min,max) {
        const range = max - min;
        const random_num = random();
        const num = min + range * random_num;
        return num;
    };
})();

經驗分享

但是,getRandomNumber的代碼功能還是非常容易改,畢竟混淆過的js依舊是裸奔着的,這一點是避免不了的。
比如說,我們都知道知乎可以設置文章的權限,例如禁止轉載之類的,有一次我看到這篇知乎文章應對流量劫持,前端能做哪些工作?,想復制代碼下來運行下卻被告知禁止轉載,打開chrome控制台查看代碼塊內容,可惜代碼內容變成富文本內容形式,於是我另尋法子,直接在js上面修改。

首先,定位到復制功能觸發的文件和代碼段。

第二步,在控制台Sources->Overrides下,打勾Enable Local Overrides

第三步,在控制台下面Sources->Page下,找到剛剛定位到的代碼文件,右鍵Save for overrides

第四步,直接把handleCopy的處理邏輯注釋掉,在控制台的Overrides下直接修改,或者在選定保存的本地文件夾中修改。


最后,保存修改后的代碼,刷新頁面,即可復制成功。

var SriPlugin = require('webpack-subresource-integrity');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ScriptExtInlineHtmlWebpackPlugin = require('script-ext-inline-html-webpack-plugin');
var ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
var path = require('path');
var WebpackAssetsManifest = require('webpack-assets-manifest');
var writeJson = require('write-json');

Reference

socket.io
白盒加密
webpack-obfuscator
JavaScript根據種子生成隨機數實現方法
瀏覽器地址欄運行JavaScript代碼
在 Chrome DevTools 中調試 JavaScript 入門


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM