鐵人賽 Day 26 逆向實戰 - URL 驗證參數 (困難)

鐵人賽 Day 26 逆向實戰 - URL 驗證參數 (困難)
鐵人賽 Day 26 逆向實戰 - URL 驗證參數 (困難)

本系列文章所討論的 JavaScript 資安與逆向工程技術,旨在分享知識、探討防禦之道,並促進技術交流。

所有內容僅供學術研究與學習,請勿用於任何非法或不道德的行為。

讀者應對自己的行為負完全責任。尊重法律與道德規範是所有技術人員應共同遵守的準則。

 

 aHR0cHM6Ly93d3cubWFzaGFuZ3BhLmNvbS9wcm9ibGVtLWRldGFpbC8xNC8=

在 Chrome DevTools 打開 「 Network 」 分頁,並篩選 「 XHR / Fetch 」 請求。

可以看到 /data/?page=3&m=... 的 API 呼叫。

「 m 」這個就是驗證參數。

1.jpg

切換到 Payload 標籤,這裡看到送出的 Form Data 僅包含 { "page": 2 },沒有其他驗證參數。

2.jpg

在 Initiator 分頁檢查呼叫堆疊追蹤到 loadPage 函式來自 pagination14.js。

點擊 pagination14.js 並進入該對應的函式。

3.jpg

打開 pagination14.js 找到 loadPage。

這裡只是透過 $.ajax 發起請求並沒有驗證參數生成的邏輯。

4.jpg

在 Console 中輸入 $.ajax 並點擊跳轉到 jQuery 原始程式碼中定義的 AJAX 函式。

5.jpg

在 jquery-3.6.0.min.js 中看到 $.ajax 的原始碼沒有被 hook,代表請求仍走 jQuery 的原流程。

6.jpg

在 pagination14.js 中的 $.ajax 下斷點並逐步執行,會發現呼叫流程跳進另一個檔案 xhr.js。

在 xhr.js 內找到混淆過的函式呼叫 x(f),進一步觀察發現這就是驗證參數生成的入口。

7.jpg

將滑鼠放上 x 會顯示該函式資訊並點擊跳轉到該函式。

8.jpg

將 xhr.js 內部的混淆程式碼解讀後,就能完整理解驗證參數的生成邏輯,掌握請求驗證的核心機制。

9.jpg
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://xxxxxxxxxx/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=xxxxxxxxxx",
            "Referer": "https://xxxxxxxxxx/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

 

作者頭像
Nick

擅長從前端的互動設計到後端的資料處理,都能親自規劃與實作。對我來說開發網站不只是完成功能,而是打造一個能被真正使用、體驗順暢的平台。我喜歡把複雜的技術轉化成簡單好懂的成果,並在這個過程中持續學習與挑戰自己。