JavaScript代碼中兩種常見的安全技術(shù):混淆和反混淆
處理JavaScript代碼時(shí),混淆(Obfuscation)和反混淆(Deobfuscation)是兩種常見的安全技術(shù)?;煜ǔS糜谔岣叽a的復(fù)雜度,從而降低其可讀性和可維護(hù)性,以防止未經(jīng)授權(quán)的訪問或理解代碼的邏輯。反混淆則是試圖恢復(fù)或至少部分理解被混淆的代碼的過程。
Obfuscation 混淆
JavaScript 混淆(Obfuscation)是指通過一系列技術(shù)手段,使 JS 代碼變得難以理解和分析,增加代碼的復(fù)雜性和混淆度,阻礙逆向工程和代碼盜用。實(shí)際上就是一種保護(hù) JS 代碼的手段。
那為什么我們需要保護(hù) JS 代碼呢 ???
JS 最早被設(shè)計(jì)出來就是為了在客戶端運(yùn)行,直接以源碼的形式傳遞給客戶端,如果不做處理則完全公開透明,任何人都可以讀、分析、復(fù)制、盜用,甚至篡改源碼與數(shù)據(jù),這是網(wǎng)站開發(fā)者不愿意看到的。
起源
早期的 JS 代碼承擔(dān)功能少,邏輯簡單且體積小,不需要保護(hù)。但隨著技術(shù)的發(fā)展,JS 承擔(dān)的功能越來越多, 文件體積增大。為了優(yōu)化用戶體驗(yàn),開發(fā)者們想了很多辦法去減小 JS 文件體積,以加快 HTTP 傳輸速度。JS 壓縮(Minification)技術(shù)應(yīng)運(yùn)而生。
常見的 JS 壓縮手段很多,比如:
刪除 JS 代碼中的空格、換行與注釋;替換 JS 代碼中的局部變量名;合并 JS 文件;……
壓縮工具開發(fā)的初衷是減小 JS 文件體積,但 JS 代碼經(jīng)過壓縮替換后,其可讀性也大大降低,間接起到了保護(hù)代碼的作用。但是后來主流瀏覽器的開發(fā)者工具都提供了格式化代碼的功能,壓縮技術(shù)所能提供的安全保護(hù)收效甚微。于是專門保護(hù) JS 代碼的技術(shù):JS 加密和 JS 混淆。
本文不會(huì)介紹 JS 加密技術(shù),只需要知道這兩種技術(shù)相輔相成,不預(yù)先進(jìn)行混淆的 JS 加密沒有意義。
常見混淆手段
變量名/函數(shù)名的替換,通過將有意義的變量名和函數(shù)名替換為隨機(jī)生成的名稱。
/* function calculateArea(radius) { return Math.PI * radius * radius; } console.log(calculateArea(5)); */ function _0x2d8f05(_0x4b083b) { return Math.PI * _0x4b083b * _0x4b083b; } console.log(_0x2d8f05(5));
字符串混淆,將代碼中的字符串替換為編碼或加密的形式,可以防止字符串被輕易讀取。
// console.log("Hello, world!"); console.log("\x48\x65\x6c\x6c\x6f\x2c\x20\x77\x6f\x72\x6c\x64\x21");
控制流混淆,改變代碼的執(zhí)行順序或結(jié)構(gòu)。例如,可以使用條件語句和循環(huán)語句來替換簡單的賦值操作。
/* let a = 1; let b = 2; let c = a + b; console.log(c); */ let a = 1; let b = 2; let c; if (a === 1) { if (b === 2) { c = a + b; } } console.log(c);
死代碼插入,即在源碼插入一些不會(huì)被執(zhí)行的代碼。
/* let a = 1; let b = 2; let c = a + b; console.log(c); */ let a = 1; let b = 2; if (false) { console.log(a - b); } let c = a + b; console.log(c);
代碼轉(zhuǎn)換,將代碼轉(zhuǎn)換為等價(jià)的,但更難理解的形式。
/* let a = 1; let b = 2; let c = a + b; console.log(c); */ let a = 1; let b = 2; let c = a - (-b); console.log(c);
常見反調(diào)試手段
實(shí)現(xiàn)防止他人調(diào)試、動(dòng)態(tài)分析自己的代碼,我們可以預(yù)先在代碼中做處理,防止用戶調(diào)試代碼。
無限 debugger。比如寫個(gè)定時(shí)器死循環(huán)禁止調(diào)試。
var c = new RegExp("1"); c.toString = function () { alert("檢測到調(diào)試") setInterval(function() { debugger }, 1000); } console.log(c);
內(nèi)存耗盡。更隱蔽的反調(diào)試手段,代碼運(yùn)行造成的內(nèi)存占用會(huì)越來越大,很快會(huì)使瀏覽器崩潰。
var startTime = new Date(); debugger; var endTime = new Date(); var isDev = endTime - startTime > 100; var stack = []; if (isDev) { while (true) { stack.push(this); console.log(stack.length, this); } }
檢測函數(shù)、對象屬性修改。攻擊者在調(diào)試的時(shí),經(jīng)常會(huì)把防護(hù)的函數(shù)刪除,或者把檢測數(shù)據(jù)對象進(jìn)行篡改??梢詸z測函數(shù)內(nèi)容,在原型上設(shè)置禁止修改。
function eval() { [native code] } window.eval = function(str) { console.log("[native code]"); }; window.eval = function(str) { }; window.eval.toString = function() { return `function eval() {[native code]}` }; function hijacked(fun) { return "prototype" in fun || fun.toString().replace(/\n|\s/g, "") != "function" + fun.name + "() {[nativecode]}"; }
前端開發(fā)中的混淆
在 Web 前端開發(fā)中,開發(fā)者會(huì)對代碼進(jìn)行壓縮和混淆,對代碼進(jìn)行優(yōu)化,并提高安全性。已經(jīng)有很多成熟的工具可以使用,比如 UglifyJS 和 JavaScript Obfuscator。
混淆通常在項(xiàng)目的構(gòu)建過程中進(jìn)行。例如,我們使用 Vite 作為模塊打包工具,就可以在 vite 的配置文件中添加UglifyJS 插件。這樣,在每次構(gòu)建項(xiàng)目時(shí),UglifyJS就會(huì)自動(dòng)對你的代碼進(jìn)行混淆。
先安裝插件。
npm install vite-plugin-uglify --save-dev
然后在配置文件中添加該插件。
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import VitePluginUglify from 'vite-plugin-uglify' export default defineConfig({ plugins: [ vue(), VitePluginUglify() ] })
在這個(gè)配置文件中,VitePluginUglify
被添加到了plugins
數(shù)組中,所以在構(gòu)建過程中,Vite 會(huì)自動(dòng)使用vite-plugin-uglify
對代碼進(jìn)行混淆。
在線混淆工具
有些站點(diǎn)提供了在線混淆的功能,比如 Free JavaScript Obfuscator,提供 JS 代碼即可得到混淆后的結(jié)果。這個(gè)站點(diǎn)的混淆基于上面提到的 JavaScript Obfuscator 實(shí)現(xiàn)。
function fibonacci(n) { let fib = [0, 1]; for (let i = 2; i <= n; i++) { fib[i] = fib[i - 1] + fib[i - 2]; } return fib; } // the first 10 numbers in the Fibonacci sequence console.log(fibonacci(10));
以上代碼的作用是計(jì)算斐波那契數(shù)列的前 10 個(gè)值并打印出來,經(jīng)過混淆可得以下內(nèi)容,可讀性肉眼可見的降低:
const _0x323128=_0x5512;(function(_0x589643,_0x5459af){const _0x1b79b8=_0x5512,_0x3e96ed=_0x589643();while(!![]){try{const _0x1fb1b3=-parseInt(_0x1b79b8(0x1f1))/0x1*(-parseInt(_0x1b79b8(0x1ea))/0x2)+-parseInt(_0x1b79b8(0x1ec))/0x3*(parseInt(_0x1b79b8(0x1f3))/0x4)+-parseInt(_0x1b79b8(0x1ed))/0x5*(parseInt(_0x1b79b8(0x1f2))/0x6)+-parseInt(_0x1b79b8(0x1e8))/0x7+parseInt(_0x1b79b8(0x1e9))/0x8*(-parseInt(_0x1b79b8(0x1f4))/0x9)+parseInt(_0x1b79b8(0x1f0))/0xa+-parseInt(_0x1b79b8(0x1ef))/0xb*(-parseInt(_0x1b79b8(0x1ee))/0xc);if(_0x1fb1b3===_0x5459af)break;else _0x3e96ed['push'](_0x3e96ed['shift']());}catch(_0x56184c){_0x3e96ed['push'](_0x3e96ed['shift']());}}}(_0x138e,0xdf35a));function _0x138e(){const _0x3a0863=['354072hRaVAZ','9mNckCh','1622341lDdscp','2787864kenYBK','546362IExhCV','log','3fofuVm','1946005vlrFyq','516IsqKpc','725241tPbpzZ','316200mzqtLe','1mgkmrs','24Zwposp'];_0x138e=function(){return _0x3a0863;};return _0x138e();}function fibonacci(_0x1b3125){let _0x9e88df=[0x0,0x1];for(let _0x406b50=0x2;_0x406b50<=_0x1b3125;_0x406b50++){_0x9e88df[_0x406b50]=_0x9e88df[_0x406b50-0x1]+_0x9e88df[_0x406b50-0x2];}return _0x9e88df;}function _0x5512(_0x2d5465,_0x1d0a2f){const _0x138ec4=_0x138e();return _0x5512=function(_0x5512ef,_0x5e1f2e){_0x5512ef=_0x5512ef-0x1e8;let _0x4be64a=_0x138ec4[_0x5512ef];return _0x4be64a;},_0x5512(_0x2d5465,_0x1d0a2f);}console[_0x323128(0x1eb)](fibonacci(0xa));
Deobfuscator 反混淆
JS 反混淆(Deobfuscator )是指對經(jīng)過混淆處理的代碼進(jìn)行還原和解析,以恢復(fù)其可讀性。Deobfuscator 可以通過對代碼進(jìn)行靜態(tài)分析和動(dòng)態(tài)分析等方式來實(shí)現(xiàn)。需要注意的是,Obfuscation 只能降低可讀性,不能完全避免逆向攻擊,而 Deobfuscator 也并不能完全還原混淆過的代碼。
只要耐心分析,多數(shù)混淆過的 JS 已然能還原出來。
在線反混淆工具
反混淆要有些趁手的工具。最常用的是瀏覽器自帶的開發(fā)者工具,其次是一些轉(zhuǎn)換混淆過的代碼的工具。以下網(wǎng)站提供在線反混淆 JS 代碼的功能:
javascript-deobfuscatorRaz1ner JavaScript Deobfuscatorsynchrony deobuscatorjs-beauty
以我們經(jīng)過混淆的代碼為例,丟進(jìn)上述第一個(gè)網(wǎng)站,可以得到以下反混淆過的代碼:
function fibonacci(jayandre) { let ramonita = [0, 1]; for (let ancel = 2; ancel <= jayandre; ancel++) { ramonita[ancel] = ramonita[ancel - 1] + ramonita[ancel - 2]; } return ramonita; } console.log(fibonacci(10));
原本的邏輯已經(jīng)較為清晰的展現(xiàn)了。當(dāng)然也有一些庫能用來反混淆本地 JS 文件,這里不多做介紹,感覺在線工具就夠用了。
開發(fā)者工具
上面的反混淆站點(diǎn)只是輔助,真反混淆還得靠瀏覽器自帶的開發(fā)者工具。接下來以chrome瀏覽器為例講講怎么用。
在反混淆過程中,我們主要使用源代碼(Source)和網(wǎng)絡(luò)(Network)這兩個(gè)模塊。Network 用于查找我們進(jìn)行用戶操作時(shí)調(diào)用了哪些 API,在調(diào)用 API 前后運(yùn)行了哪些 JS 文件;Source 提供了網(wǎng)站整體的 JS 代碼及靜態(tài)資源,我們的反混淆分析工作主要就在這里進(jìn)行。
在 Source 模塊中,默認(rèn)ctrl+shift+p
可以開啟開發(fā)者工具的命令行,我們可以找到兩個(gè)“搜索”工具,分別對應(yīng)“全局搜索”和“在當(dāng)前文件中搜索”,很適合查找指定字段。
開發(fā)者工具提供了替換(Override)功能,開啟本地替換選項(xiàng),上傳自己的目錄,然后選中瀏覽器中指定 JS 文件,做出修改后ctrl+s
保存,即可將源文件保存到我們自己的目錄中,之后對文件做出的修改可以直接替換對應(yīng)的原文件,這樣就能方便的修改瀏覽器端 JS 文件。
剩下的就是動(dòng)調(diào)了,后面會(huì)舉例子解釋。
靜/動(dòng)態(tài)調(diào)試
先做個(gè)區(qū)分,逆網(wǎng)頁的 JS 代碼更多得是在開發(fā)者工具中做動(dòng)調(diào)的。
靜態(tài)調(diào)試:靜態(tài)調(diào)試是通過分析代碼的結(jié)構(gòu)和邏輯來理解其功能。這種方法不需要運(yùn)行代碼,只需要對代碼進(jìn)行分析和理解。例如,可以通過反匯編工具將二進(jìn)制的可執(zhí)行文件翻譯成匯編代碼,通過對代碼的分析來破解軟件。動(dòng)態(tài)調(diào)試:動(dòng)態(tài)調(diào)試則是在代碼運(yùn)行時(shí)進(jìn)行的。通過設(shè)置斷點(diǎn),單步執(zhí)行,觀察變量的值變化等方式,來理解代碼的運(yùn)行過程和邏輯。動(dòng)態(tài)調(diào)試可以有效應(yīng)對多數(shù)混淆措施,從中還原出運(yùn)行邏輯,是逆向分析的關(guān)鍵手段。前面說的反調(diào)試便是阻攔動(dòng)態(tài)調(diào)試。
實(shí)戰(zhàn)
百度翻譯接口
未登錄狀態(tài)下翻譯字符串,觀察 Network 可以找到/v2transapi
POST 請求報(bào)文,其 payload 中表單的 query
字段即為我們輸入待翻譯的字符串。
刷新頁面多次翻譯,發(fā)現(xiàn)只有sign
字段的值在隨query
一直變化,transtype
的值會(huì)根據(jù)觸發(fā)翻譯的方式在realtime
和enter
之間切換,其它字段值保持不變。我們接下來的任務(wù)就是分析sign
字段的值是怎么來的。
為了搞清楚sign
是如何生成的,我們需要在 Sources 模塊中全局搜索sign
字段。但因?yàn)?code>sign本身是一個(gè)常見的字段,我們很容易定位到其他與表單無關(guān)的地方。這里有一個(gè)小技巧,為了獲得參數(shù)相關(guān)代碼,我們可以搜索sign:
或者sign=
,以盡量避免定位到無關(guān)代碼。
在 Sources 模塊中全局搜索sign:
,定位到很多文件,根據(jù)文件名和文件內(nèi)容,可以判斷最有可能在 index.36217dc5.js 文件中,而該文件中出現(xiàn)了 6 處sign:
相關(guān)代碼,依次打斷點(diǎn)并執(zhí)行翻譯操作,發(fā)現(xiàn)只會(huì)在 25800 行處的sign: b(e);
處停下:
單步步進(jìn),可以發(fā)現(xiàn)參數(shù) t 值即為傳入的字符串:
把這段函數(shù)抽離出來,寫到一個(gè) main.js 文件中,調(diào)用該函數(shù)并運(yùn)行:
b = function(t) { var o, i = t.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g); if (null === i) { var a = t.length; a > 30 && (t = "".concat(t.substr(0, 10)).concat(t.substr(Math.floor(a / 2) - 5, 10)).concat(t.substr(-10, 10))) } else { for (var s = t.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/), c = 0, u = s.length, l = []; c < u; c++) "" !== s[c] && l.push.apply(l, function(t) { if (Array.isArray(t)) return e(t) }(o = s[c].split("")) || function(t) { if ("undefined" != typeof Symbol && null != t[Symbol.iterator] || null != t["@@iterator"]) return Array.from(t) }(o) || function(t, n) { if (t) { if ("string" == typeof t) return e(t, n); var r = Object.prototype.toString.call(t).slice(8, -1); return "Object" === r && t.constructor && (r = t.constructor.name), "Map" === r || "Set" === r ? Array.from(t) : "Arguments" === r || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r) ? e(t, n) : void 0 } }(o) || function() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.") }()), c !== u - 1 && l.push(i[c]); var p = l.length; p > 30 && (t = l.slice(0, 10).join("") + l.slice(Math.floor(p / 2) - 5, Math.floor(p / 2) + 5).join("") + l.slice(-10).join("")) } for (var d = "".concat(String.fromCharCode(103)).concat(String.fromCharCode(116)).concat(String.fromCharCode(107)), h = (null !== r ? r : (r = window[d] || "") || "").split("."), f = Number(h[0]) || 0, m = Number(h[1]) || 0, g = [], y = 0, v = 0; v < t.length; v++) { var _ = t.charCodeAt(v); _ < 128 ? g[y++] = _ : (_ < 2048 ? g[y++] = _ >> 6 | 192 : (55296 == (64512 & _) && v + 1 < t.length && 56320 == (64512 & t.charCodeAt(v + 1)) ? (_ = 65536 + ((1023 & _) << 10) + (1023 & t.charCodeAt(++v)), g[y++] = _ >> 18 | 240, g[y++] = _ >> 12 & 63 | 128) : g[y++] = _ >> 12 | 224, g[y++] = _ >> 6 & 63 | 128), g[y++] = 63 & _ | 128) } for (var b = f, w = "".concat(String.fromCharCode(43)).concat(String.fromCharCode(45)).concat(String.fromCharCode(97)) + "".concat(String.fromCharCode(94)).concat(String.fromCharCode(43)).concat(String.fromCharCode(54)), k = "".concat(String.fromCharCode(43)).concat(String.fromCharCode(45)).concat(String.fromCharCode(51)) + "".concat(String.fromCharCode(94)).concat(String.fromCharCode(43)).concat(String.fromCharCode(98)) + "".concat(String.fromCharCode(43)).concat(String.fromCharCode(45)).concat(String.fromCharCode(102)), x = 0; x < g.length; x++) b = n(b += g[x], w); return b = n(b, k), (b ^= m) < 0 && (b = 2147483648 + (2147483647 & b)), "".concat((b %= 1e6).toString(), ".").concat(b ^ f) } const query = "abandon"; console.log(b(query))
運(yùn)行時(shí)報(bào)錯(cuò),提示r
未定義。在繼續(xù)動(dòng)調(diào)去找r
是什么。步進(jìn)調(diào)試到這一步時(shí),發(fā)現(xiàn)r
被賦值為window[d]
,即 “320305.131321201”,在此之前其值一直為null。
我們可以發(fā)現(xiàn)d
的值為gtk
。我們本地是通過 Node.js 運(yùn)行 JS 腳本,沒有window[]
這種 Web API,所以直接將320305.131321201
硬編碼進(jìn)去。在此運(yùn)行腳本,又會(huì)提示缺少n
函數(shù):
我們在面板中找到n
函數(shù),光標(biāo)懸浮于上方可直接跳轉(zhuǎn)到函數(shù)聲明的地方:
找到n
函數(shù)后將其添加到 JS 腳本中,再次運(yùn)行,即可得到結(jié)果103339.356506
,這與我們在 Network 模塊中查看到的sign
值相同。
最終腳本如下,輸入query
的值即可得到請求/v2transapi
所需的 payload:
/** * function to generate sign */ n = function (t, e) { for (var n = 0; n < e.length - 2; n += 3) { var r = e.charAt(n + 2); r = "a" <= r ? r.charCodeAt(0) - 87 : Number(r), r = "+" === e.charAt(n + 1) ? t >>> r : t << r, t = "+" === e.charAt(n) ? t + r & 4294967295 : t ^ r } return t } b = function(t) { var o, i = t.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g); if (null === i) { var a = t.length; a > 30 && (t = "".concat(t.substr(0, 10)).concat(t.substr(Math.floor(a / 2) - 5, 10)).concat(t.substr(-10, 10))) } else { for (var s = t.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/), c = 0, u = s.length, l = []; c < u; c++) "" !== s[c] && l.push.apply(l, function(t) { if (Array.isArray(t)) return e(t) }(o = s[c].split("")) || function(t) { if ("undefined" != typeof Symbol && null != t[Symbol.iterator] || null != t["@@iterator"]) return Array.from(t) }(o) || function(t, n) { if (t) { if ("string" == typeof t) return e(t, n); var r = Object.prototype.toString.call(t).slice(8, -1); return "Object" === r && t.constructor && (r = t.constructor.name), "Map" === r || "Set" === r ? Array.from(t) : "Arguments" === r || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r) ? e(t, n) : void 0 } }(o) || function() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.") }()), c !== u - 1 && l.push(i[c]); var p = l.length; p > 30 && (t = l.slice(0, 10).join("") + l.slice(Math.floor(p / 2) - 5, Math.floor(p / 2) + 5).join("") + l.slice(-10).join("")) } for (var d = "".concat(String.fromCharCode(103)).concat(String.fromCharCode(116)).concat(String.fromCharCode(107)), h = (r = "320305.131321201").split("."), f = Number(h[0]) || 0, m = Number(h[1]) || 0, g = [], y = 0, v = 0; v < t.length; v++) { var _ = t.charCodeAt(v); _ < 128 ? g[y++] = _ : (_ < 2048 ? g[y++] = _ >> 6 | 192 : (55296 == (64512 & _) && v + 1 < t.length && 56320 == (64512 & t.charCodeAt(v + 1)) ? (_ = 65536 + ((1023 & _) << 10) + (1023 & t.charCodeAt(++v)), g[y++] = _ >> 18 | 240, g[y++] = _ >> 12 & 63 | 128) : g[y++] = _ >> 12 | 224, g[y++] = _ >> 6 & 63 | 128), g[y++] = 63 & _ | 128) } for (var b = f, w = "".concat(String.fromCharCode(43)).concat(String.fromCharCode(45)).concat(String.fromCharCode(97)) + "".concat(String.fromCharCode(94)).concat(String.fromCharCode(43)).concat(String.fromCharCode(54)), k = "".concat(String.fromCharCode(43)).concat(String.fromCharCode(45)).concat(String.fromCharCode(51)) + "".concat(String.fromCharCode(94)).concat(String.fromCharCode(43)).concat(String.fromCharCode(98)) + "".concat(String.fromCharCode(43)).concat(String.fromCharCode(45)).concat(String.fromCharCode(102)), x = 0; x < g.length; x++) b = n(b += g[x], w); return b = n(b, k), (b ^= m) < 0 && (b = 2147483648 + (2147483647 & b)), "".concat((b %= 1e6).toString(), ".").concat(b ^ f) } /** * test */ const query = "abandon"; console.log(`from=en&to=zh&query=${query}&simple_means_flag=3&sign=${b(query)}&token=14025658070b41f40739347cef0ec62a&domain=common&ts=1708512893507`)
掘金登錄接口
登錄時(shí)抓包,可以得到對/passport/web/user/login
接口的請求報(bào)文:
# GET 查詢字符串參數(shù) aid: 2608 account_sdk_source: web sdk_version: 2.2.6 verifyFp: verify_lsom0d3u_s6mZvQBP_pamX_41TO_81V1_VRng2UjxFI79 fp: verify_lsom0d3u_s6mZvQBP_pamX_41TO_81V1_VRng2UjxFI79 sign: d9116c9cae3fcdf848f1288e1850eb2a489a4e23ece930692912a8bc155d89ec qs: 6466666a706b715a76616e5a766a7077666029646c612963752976616e5a736077766c6a6b297360776c637c4375 # POST 表單參數(shù) mix_mode: 1 account: 34363d3336373d3d343c3c password: 343736343736343736 fixed_mix_mode: 1
流程其實(shí)大差不差,就是搜參數(shù)、打斷點(diǎn)、慢慢動(dòng)調(diào),基本都能找出來。掘金登錄只需要 POST 表單參數(shù)正確即可,GET 參數(shù)不對也能過。以上參數(shù)中,會(huì)動(dòng)態(tài)變化的只有sign
、account
和password
,其中 GET 參數(shù)sign
即使刪掉也能過登錄驗(yàn)證。
具體過程不再貼圖展示,這里直接提供獲取 POST 表單參數(shù)的腳本,感興趣的可以嘗試去逆一下sign
是如何生成的,難度比逆account
和password
要高一些:
/** * raw data */ const account = '00000000000' const password = '1q2w3e' /** * handle account and password */ var T = function(e) { var t, n = []; if (void 0 === e) return ""; t = function(e) { for (var t, n = e.toString(), r = [], a = 0; a < n.length; a++) 0 <= (t = n.charCodeAt(a)) && t <= 127 ? r.push(t) : 128 <= t && t <= 2047 ? (r.push(192 | 31 & t >> 6), r.push(128 | 63 & t)) : (2048 <= t && t <= 55295 || 57344 <= t && t <= 65535) && (r.push(224 | 15 & t >> 12), r.push(128 | 63 & t >> 6), r.push(128 | 63 & t)); for (var i = 0; i < r.length; i++) r[i] &= 255; return r }(e); for (var r = 0, a = t.length; r < a; ++r) n.push((5 ^ t[r]).toString(16)); return n.join("") } /** * obtain the post form */ const postForm = `mix_mode=1&account=${T(account)}&password=${T(password)}&fixed_mix_mode=1` console.log(postForm)
總結(jié)
到此這篇關(guān)于JavaScript代碼中兩種常見的安全技術(shù):混淆和反混淆的文章就介紹到這了,更多相關(guān)JavaScript代碼中混淆和反混淆技術(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Javascript打印網(wǎng)頁部分內(nèi)容的腳本
有時(shí)候我們只需要打印部分內(nèi)容,因?yàn)楝F(xiàn)在的頁面中廣告和一些相關(guān)內(nèi)容很多,所有用下面的方法,就可以了2008-11-11javascript實(shí)現(xiàn)的樣式表(CSS) 格式整理與壓縮
javascript實(shí)現(xiàn)的樣式表(CSS) 格式整理與壓縮,可以分為多行與單行,非常不錯(cuò)。2010-05-05微信小程序?qū)崿F(xiàn)點(diǎn)擊按鈕修改字體顏色功能【附demo源碼下載】
這篇文章主要介紹了微信小程序?qū)崿F(xiàn)點(diǎn)擊按鈕修改字體顏色功能,涉及微信小程序wx:for循環(huán)讀取data數(shù)值及事件綁定修改元素屬性相關(guān)操作技巧,需要的朋友可以參考下2017-12-12jquery中validate與form插件提交的方式小結(jié)
這篇文章主要介紹了jquery中validate與form插件提交的方式小結(jié),需要的朋友可以參考下2016-03-03瀏覽器JavaScript調(diào)試功能無法使用解決方案
這篇文章主要介紹了瀏覽器JavaScript調(diào)試功能無法使用解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09JS實(shí)現(xiàn)的適合做faq或menu滑動(dòng)效果示例
這篇文章主要介紹了JS實(shí)現(xiàn)的適合做faq或menu滑動(dòng)效果,結(jié)合實(shí)例形式分析了基于JS實(shí)現(xiàn)的頁面元素滑動(dòng)漸變效果的相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-11-11elementui上傳圖片回顯功能實(shí)現(xiàn)
這篇文章主要介紹了elementui上傳圖片回顯,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07