鐵人賽 Day 22 逆向實戰 - Header 驗證參數 + Response加密 (簡單)

鐵人賽 Day 22 逆向實戰 - Header 驗證參數 + Response加密 (簡單)
鐵人賽 Day 22 逆向實戰 - Header 驗證參數 + Response加密 (簡單)

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

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

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

 

aHR0cHM6Ly93d3cubWFzaGFuZ3BhLmNvbS9wcm9ibGVtLWRldGFpbC82Lw==

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

可以看到 Method 為 GET 且 URL 沒有任何驗證餐數。

1.jpg

查看 Request Headers 可以看到兩個關鍵欄位。

s:驗證參數。

tt:時間戳。

2.jpg

伺服器回傳一段加密字串 t 需要解密後才能得到真實資料。

3.jpg

按下 ESC 打開底部工具欄透過 Search 搜尋 s:。

搜尋結果顯示 pagination6.js 檔案裡有 s: window.token 的程式碼。

這代表 s 的值,實際上是 window.token。

4.jpg

發現 token 是透過 xxoo 函式運算後產生並與時間戳組合存入 window.hhh。

5.jpg

在第 92 行 (window.token = window.xxoo(...)) 打斷點。

切換分頁時程式會停在這邊,滑鼠移到 xxoo 上方會顯示該函式資訊。

點擊後即可跳轉到 xxoo 函式所在的行數。

6.jpg

在第 76 行打斷點後按下「Resume script execution」後進入函式並且馬上斷著。

可在 Scope → Local 看到傳入參數。

n:sssssbbbbb1756951474291

r:undefined

t:undefined

7.jpg

使用「Step into next function call」逐步追蹤可以發現實際會執行 h(l(n)) 函式。

按下 ESC 打開底部工具欄在 Console 中輸入 n 可以查看參數初始值再次輸入 h(l(n)) 可以得到加密過後的值為 "9e93f4ebddf3803e2ec4f248d34e1378"。

8.jpg

此數值長度為32位非常類似MD5 我們可以實際測試將 "sssssbbbbb1756951474291" 使用NodeJS進行MD5,實際運算後發現結果與 h(l(n)) 的結果相同證實 h(l(n)) 確實為MD5。

const CryptoJS = require('crypto-js');

const message = "sssssbbbbb1756951474291";
const md5Hash = CryptoJS.MD5(message).toString();

console.log(`MD5 雜湊值: ${md5Hash}`);

在第 124 行 (JSON.parse(xxxoooo(data.t))) 打斷點。

切換分頁時程式會停在這邊,點擊後即可跳轉到 xxxoooo 函式所在的行數。

9.jpg

在 xxxoooo 函式內看到使用 AES 解密。

kkkk 與 iiii 作為 key 與 iv 透過 AES.decrypt 解密伺服器回傳的 t 即可得到真實資料。

10.jpg
const CryptoJS = require('crypto-js')

function generatorHeaderS(time) {
    const data = "sssssbbbbb" + time

    return CryptoJS.MD5(data).toString()
}

const decrypt = (encryptedHex) => {
    let key = CryptoJS.enc.Utf8.parse("xxxxxxxxoooooooo");
    let iv = CryptoJS.enc.Utf8.parse("0123456789ABCDEF");

    let parseEncryptedHex = CryptoJS.enc.Hex.parse(encryptedHex);
    let decryptBuffer = CryptoJS.AES.decrypt({
            ciphertext: parseEncryptedHex
        },
        key,
        {
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7,
            iv: iv,
        });
    return decryptBuffer.toString(CryptoJS.enc.Utf8);
}

const getPage = async(page) => {
    const time = new Date().getTime();

    const response = await fetch("https://xxxxxxxxxx/api/problem-detail/6/data/?page=" + page, {
        "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/6/",
            "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",
            "s": generatorHeaderS(time),
            "tt": time
        },
        "body": null,
        "method": "GET"
    });

    let json = await response.json()

    json = JSON.parse(decrypt(json.t))

    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/6.js

 

作者頭像
Nick

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