iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
Security

走進資安現場: JavaScript資安逆向工程超實戰系列 第 22

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

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20250921/201697752ysKWpXu8M.jpg

本系列文章所討論的 JavaScript 資安與逆向工程技術,旨在分享知識、探討防禦之道,並促進技術交流。
所有內容僅供學術研究與學習,請勿用於任何非法或不道德的行為。
讀者應對自己的行為負完全責任。尊重法律與道德規範是所有技術人員應共同遵守的準則。

本文同步發佈:https://nicklabs.cc/javascript-reverse-engineering-header-response-easy

挑戰網址

aHR0cHM6Ly93d3cubWFzaGFuZ3BhLmNvbS9wcm9ibGVtLWRldGFpbC82Lw==

解題過程

觀察 Network 請求

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775tLTh0GMMp1.jpg

查看 Request Headers

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

s:驗證參數。

tt:時間戳。

https://ithelp.ithome.com.tw/upload/images/20250921/201697754D0iUcJgOi.jpg

查看 Request Response

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

https://ithelp.ithome.com.tw/upload/images/20250921/201697759rh5E7W6k3.jpg

搜尋參數位置

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

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775HcYvmGeGVB.jpg

找到加密位置

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775El9i63BU8S.jpg

設置斷點

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

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775I1ZSLjKxZl.jpg

進入 xxoo 函式

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

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

n:sssssbbbbb1756951474291

r:undefined

t:undefined

https://ithelp.ithome.com.tw/upload/images/20250921/201697751jo3HdHLTo.jpg

逐步執行觀察輸出

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775MuZt9bIi1R.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}`);

Response 解密流程

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775B1UG5X1dCN.jpg

分析 AES 解密函式

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

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

https://ithelp.ithome.com.tw/upload/images/20250921/20169775IKQ0ACz1Hg.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()

Github 原始碼

https://github.com/mrnick6886/ScrapingChallenges/blob/main/mashangpa/6.js


上一篇
Day 21 逆向實戰 - 解析微混淆 + AES簽名機制流程 (簡單)
下一篇
Day 23 逆向實戰 - Header 驗證參數 + Url 驗證參數 + Response加密 (中等)
系列文
走進資安現場: JavaScript資安逆向工程超實戰23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言