iT邦幫忙

2025 iThome 鐵人賽

DAY 20
0
Security

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

Day 20 逆向實戰 - 解析MD5簽名機制流程 (簡單)

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20250919/20169775It2ToYeAXR.jpg

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

本文同步發佈:https://nicklabs.cc/javascript-reverse-engineering-md5-api-sign-easy

挑戰網址

aHR0cHM6Ly93d3cubWFzaGFuZ3BhLmNvbS9wcm9ibGVtLWRldGFpbC80Lw==

解題過程

觀察 Network 請求

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

可以看到 /data/?page=2&sign=...&_ts=... 的 API 呼叫。

「 sign 」 與 「 _ts 」這兩個就是驗證參數。

https://ithelp.ithome.com.tw/upload/images/20250919/20169775a9vnzc1roT.jpg

搜尋 sign 的來源

Search 全域搜尋 sign。

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

這代表 API 中的 sign 值,實際上是 window.token。

https://ithelp.ithome.com.tw/upload/images/20250919/20169775XDSeMwmXm2.jpg

分析 sign 的產生邏輯

在 pagination4.js 中找到 LoadPage(pageNumber) 函式。

程式會先產生 timestamp = new Date().getTime()。

再將 "tuling" + timestamp + pageNumber 拼接成字串進行 MD5 雜湊。

最後存入 window.token,並作為 API 請求參數中的 sign。

https://ithelp.ithome.com.tw/upload/images/20250919/20169775OZm9PNBaRa.jpg

設置斷點進行驗證

在 pagination4.js 第 87 行(window.token = window.md5(...))設定斷點。

重新觸發 API 請求,程式會在這行暫停。

將"tuling" + timestamp + pageNumber 反白後滑鼠放上去,可以馬上看到運算的結果。

如下圖所示這時候的值是 "tuling17568460861013" 然後進行MD5雜湊。

https://ithelp.ithome.com.tw/upload/images/20250919/20169775CfomFeVdgx.jpg

檢查 sign 的實際值

在 Debugger 暫停狀態下,打開 Console。

輸入 window.token,即可輸出運算後的MD5。

這個值與 API 請求中的 sign 一致,驗證了 sign 的生成邏輯。

https://ithelp.ithome.com.tw/upload/images/20250919/20169775ZgBKwkIazm.jpg

檢查MD5算法是否魔改過

為了避免這是魔改MD5我們必須進行驗證

const CryptoJS = require('crypto-js')

const sign = CryptoJS.MD5("tuling17568460861013").toString();
console.log(sign);

可以得到執行結果就是 "6b6e0c21877698662f1794d9a7bebb54"。

完整程式碼

那現在即可把完整請求用Node模擬出來。

const CryptoJS = require('crypto-js')

const getPage = async(page) => {
  const timestamp = new Date().getTime()
  const sign = CryptoJS.MD5("tuling" + timestamp + page).toString();
  const url = `https://xxxxxxxxxx/api/problem-detail/4/data/?page=${page}&sign=${sign}&_ts=${timestamp}`

  const response = await fetch(url, {
    "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/4/",
      "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"
  });

  const 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()

Github 原始碼

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


上一篇
Day 19 Webpack 編譯過的程式碼是如何運作的?解析打包後的結構
下一篇
Day 21 逆向實戰 - 解析微混淆 + AES簽名機制流程 (簡單)
系列文
走進資安現場: JavaScript資安逆向工程超實戰23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言