本系列文章所討論的 JavaScript 資安與逆向工程技術,旨在分享知識、探討防禦之道,並促進技術交流。
所有內容僅供學術研究與學習,請勿用於任何非法或不道德的行為。
讀者應對自己的行為負完全責任。尊重法律與道德規範是所有技術人員應共同遵守的準則。
本文同步發佈:https://nicklabs.cc/javascript-reverse-engineering-url-difficult
aHR0cHM6Ly93d3cubWFzaGFuZ3BhLmNvbS9wcm9ibGVtLWRldGFpbC8xNC8=
在 Chrome DevTools 打開 「 Network 」 分頁,並篩選 「 XHR / Fetch 」 請求。
可以看到 /data/?page=3&m=... 的 API 呼叫。
「 m 」這個就是驗證參數。
切換到 Payload 標籤,這裡看到送出的 Form Data 僅包含 { "page": 2 },沒有其他驗證參數。
在 Initiator 分頁檢查呼叫堆疊追蹤到 loadPage 函式來自 pagination14.js。
點擊 pagination14.js 並進入該對應的函式。
打開 pagination14.js 找到 loadPage。
這裡只是透過 $.ajax 發起請求並沒有驗證參數生成的邏輯。
在 Console 中輸入 $.ajax 並點擊跳轉到 jQuery 原始程式碼中定義的 AJAX 函式。
在 jquery-3.6.0.min.js 中看到 $.ajax 的原始碼沒有被 hook,代表請求仍走 jQuery 的原流程。
在 pagination14.js 中的 $.ajax 下斷點並逐步執行,會發現呼叫流程跳進另一個檔案 xhr.js。
在 xhr.js 內找到混淆過的函式呼叫 x(f),進一步觀察發現這就是驗證參數生成的入口。
將滑鼠放上 x 會顯示該函式資訊並點擊跳轉到該函式。
將 xhr.js 內部的混淆程式碼解讀後,就能完整理解驗證參數的生成邏輯,掌握請求驗證的核心機制。
function getUrlM() {
const ts = new Date().getTime()
function t1(str) {
let n = 0;
for (let i = 0; i < str.length; i++) {
var charCode = str.charCodeAt(i);
for (let j = 0; j < 20; j++)
switch (j % 3) {
case 0:
n = n ^ (charCode << j % 8);
break;
case 1:
n = (n ^ charCode >> j % 8)
break;
case 2:
n = n ^ charCode;
}
}
return n.toString(16);
}
function t2(N) {
var W = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let O = '', L, y, k, H, D, K, S, I = 0;
while(I < N.length){
L = N.charCodeAt(I++)
y = I < N.length ? N.charCodeAt(I++) : 0
k = I < N.length ? N.charCodeAt(I++) : 0
H = L >> 2
D = (3 & L) << 4 | (y >> 4)
K = (((15 & y) << 2) | (k >> 6))
S = 63 & k
isNaN(y) ? K = S = 64 : isNaN(k) && (S = 64)
O = (O + W.charAt(H)) + W.charAt(D) + W.charAt(K) + W.charAt(S)
}
return O;
}
let p = t1('dasdasdarqwdasdasqwdasda' + ts)
return t2(`${p}${ts}`)
}
const getPage = async(page) => {
const urlM = getUrlM()
const response = await fetch(`https://www.mashangpa.com/api/problem-detail/14/data/?m=${urlM}`, {
"headers": {
"accept": "*/*",
"accept-language": "zh-TW,zh;q=0.9,en;q=0.8,en-US;q=0.7",
"cache-control": "no-cache",
"pragma": "no-cache",
"priority": "u=1, i",
"sec-ch-ua": "\"Not;A=Brand\";v=\"99\", \"Google Chrome\";v=\"139\", \"Chromium\";v=\"139\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"cookie": "sessionid=h378reowcqlena73nfa49mqfc4g26i6f",
"Referer": "https://www.mashangpa.com/problem-detail/14/",
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36",
},
"body": null,
"method": "GET"
});
let json = await response.json()
return json.current_array.reduce((a, b) => a + b, 0);
}
const run = async() => {
let total = 0;
for(let i = 1; i <= 20; i++){
total += (await getPage(i))
}
console.log(`total: ${total}`)
}
run()
https://github.com/mrnick6886/ScrapingChallenges/blob/main/mashangpa/14.js