免責宣告
本文章所提到的技術僅用於學習用途,禁止使用本文章的任何技術進行發起網路攻擊、非法利用等網路犯罪行為,一切資訊禁止用於任何非法用途。若讀者利用文章所提到的技術實施違法犯罪行為,其責任一概由讀者自行承擔,與作者無關。
0x01 前言
研究飛豬旅行HTML5端sign簽名過程,以復刻sign簽名過程為手段,以查詢酒店價格為目標,以學習技術要點、思路以及如何防範為宗旨,展開對飛豬H5端API介面sign簽名的研究。
0x02 尋突破口
捕獲目標API請求資料包
使用瀏覽器F12開發者工具
開啟裝置模擬器模式,訪問酒店搜尋頁面,隨便搜尋一家酒店並進入酒店的詳情頁面,詳情頁面請求將全部被捕獲於開發者工具的NetWork(網路)
選項卡中。
使用Ctrl+F
查詢頁面中對應價格的數字,定位到請求資料包位置。
分析資料包Payload
除了sign簽名的一串看似md5密文但實際上又不是md5密文的欄位以外,其他欄位都很好理解。但如果除了sign簽名欄位以外的其他欄位修改後,sign簽名也必須跟著改動,並且是以一個固定的加密規則
進行運算後得出來的sign簽名字串,故當務之急需要找到sign簽名的位置並把它運算的函式復刻下來,驗證加密函式的可用性。
欄位 | 值 | 盲猜意義 |
---|---|---|
type | originaljson | 應該是指請求源型別是json |
api | mtop.trip.hotel.hotelDetail | 告訴伺服器需要呼叫的api服務 |
v | 1.0 | API協議版本 |
params | [object Object] | 不懂,估計是傳值的時候把整個物件傳給params了 |
ttid | 201300@travel_h5_3.1.0 | 應該也是api所屬版本號 |
appKey | 12574478 | 顧名思義,具體用處可以檢視淘寶的API開放文件 |
t | 1641821310356 | 時間戳 |
sign | bb6db2ea281d6422409d820d8223147 | 經過一系列加密後的sign簽名 |
data(BODY) | 一大串JSON文字 | 這就是真正要傳的請求資料文字 |
定位sign簽名位置
需要找到sign簽名的運算函式,首先得找到sign簽名字串在哪裡賦的值。定位某個請求引數賦值的方法有很多種,有做hook的、有Ctrl+Shift+F的、有用XHR斷點的,等等非常多的方法,而且有一些複雜的網站往往需要將這些方法組合使用才能找到某個引數的賦值位置,而在本專案,直接使用Ctrl+Shift+F大法
即可。
嘗試使用
Ctrl+Shift+F大法
解決問題是一件價效比很高的事情,前提是你需要猜測網站js原始碼引數組合邏輯背後的過程。
本專案例子:URL的sign簽名引數,可猜測網站js原始碼拼接該引數時使用了"&sign="
、"sign="
、"sign"
。
!!此方法不一定適用於任何網站,若js原始碼混淆嚴重,失去了字串原本的意義,則無法使用該方法查詢(往後若有專案可演示其他搜尋方法)
現在,嘗試使用全域性搜尋"sign="
字串,馬上能定位到可疑的sign簽名賦值的程式碼語句。
可以看到有三條疑似給sign簽名賦值的三個js檔案,這時候需要根據逆向經驗去猜測原始碼應該會使用哪個去進行sign簽名的賦值和加密。但如果沒有逆向經驗的判斷,也可以對搜尋出來的三個js檔案可疑的程式碼段進行同時斷點,逐個檢視分析。
其實可以在Network(網路)
選項卡的請求項中檢視Initiator
裡的Request call stack
,往往最後出現最多次的js檔案,即是請求加密所執行的js檔案。在這裡,出現最多次的是seed-min.js
檔案,我們鎖定它,然後進行程式碼段的斷點。
在8548行做一個斷點,重新重新整理頁面,讓前端重新請求一個資料包並把它斷點截獲。
0x03 順藤摸瓜尋加密入口
斷點被捕獲後,在Sources(源)
選項卡中,檢視被斷點後時的Scope(本地變數)
和CallStack(呼叫棧)
,通過這兩個資訊順藤摸瓜到加密函式的入口處。
顯然,這裡的變數e
就是我們要找的sign簽名字串,於是我們從這裡開始順藤摸瓜,找到加密入口的地方。
分析方法y(i.data, m, S.a, v)
以下程式碼段很顯然,變數e
是由方法y(i.data, m, S.a, v)
運算以後得來的,所以我們得進入y的函式內部。
y(i.data, m, S.a, v).then((function(e) {
d.push("sign=" + e),
n({
originPath: f,
search: d
})
}))
把游標放在方法名上,會顯示方法所在位置,點選可以進入到對應的方法的定義位置。
方法y(e, t, n, r)
內仍然幾個值得斷點檢視的方法_(n)
、方法d([n, t, r, e].join("&"))
以及變數n
分析方法_(n)
顯然該方法是用來獲取cookies內的_m_h5_tk
的值,並且執行方法v(n)
,將_m_h5_tk
的值用下劃線"_"
分割,取左邊部分。
分析方法d([n, t, r, e].join("&"))
通過對方法d([n, t, r, e].join("&"))
處的斷點,檢視以下4個變數的值。
引數名 | 值 | 解釋 |
---|---|---|
n | af5ea7aa071afc99d96745743d3634d0 | _m_h5_tk 值的左邊 |
t | 1641959319288 | 時間戳 |
r | 12574478 | appKey |
e | {"_fli_newpage":"1","hid":"0","adultNum":2,"shid":1002342 ...... |
真正要傳的請求資料文字 |
使用.join("&")
將四個變數的值用"&"
連線在一起,傳送給方法d()
使用單步執行進入方法d()
內部,發現其傳參變數為e
,其內容為:
"af5ea7aa071afc99d96745743d3634d0&1641959319288&12574478&{"_fli_newpage":"1","hid":"0","adultNum":2,"shid":1002342 ....
由於篇幅問題,不將變數e
的值全部展開檢視,但可以發現,確實是由以上表格的四個值用"&"
拼接起來的。
// 方法d()的內部
d = (r = function(e, t) {
return e << t | e >>> 32 - t
}
,
i = function(e, t) {
var n, r, i, o, a;
return i = 2147483648 & e,
o = 2147483648 & t,
a = (1073741823 & e) + (1073741823 & t),
(n = 1073741824 & e) & (r = 1073741824 & t) ? 2147483648 ^ a ^ i ^ o : n | r ? 1073741824 & a ? 3221225472 ^ a ^ i ^ o : 1073741824 ^ a ^ i ^ o : a ^ i ^ o
}
,
o = function(e, t, n, o, a, s, c) {
return e = i(e, i(i(function(e, t, n) {
return e & t | ~e & n
}(t, n, o), a), c)),
i(r(e, s), t)
}
,
a = function(e, t, n, o, a, s, c) {
return e = i(e, i(i(function(e, t, n) {
return e & n | t & ~n
}(t, n, o), a), c)),
i(r(e, s), t)
}
,
s = function(e, t, n, o, a, s, c) {
return e = i(e, i(i(function(e, t, n) {
return e ^ t ^ n
}(t, n, o), a), c)),
i(r(e, s), t)
}
,
c = function(e, t, n, o, a, s, c) {
return e = i(e, i(i(function(e, t, n) {
return t ^ (e | ~n)
}(t, n, o), a), c)),
i(r(e, s), t)
}
,
u = function(e) {
var t, n = "", r = "";
for (t = 0; t <= 3; t++)
n += (r = "0" + (e >>> 8 * t & 255).toString(16)).substr(r.length - 2, 2);
return n
}
,
function(e) {
var t, n, r, l, p, f, d, h, g, m;
for (e = function(e) {
e = e.replace(/\r\n/g, "\n");
for (var t = "", n = 0; n < e.length; n++) {
var r = e.charCodeAt(n);
r < 128 ? t += String.fromCharCode(r) : r > 127 && r < 2048 ? (t += String.fromCharCode(r >> 6 | 192),
t += String.fromCharCode(63 & r | 128)) : (t += String.fromCharCode(r >> 12 | 224),
t += String.fromCharCode(r >> 6 & 63 | 128),
t += String.fromCharCode(63 & r | 128))
}
return t
}(e),
t = function(e) {
for (var t, n = e.length, r = n + 8, i = 16 * ((r - r % 64) / 64 + 1), o = new Array(i - 1), a = 0, s = 0; s < n; )
a = s % 4 * 8,
o[t = (s - s % 4) / 4] = o[t] | e.charCodeAt(s) << a,
s++;
return a = s % 4 * 8,
o[t = (s - s % 4) / 4] = o[t] | 128 << a,
o[i - 2] = n << 3,
o[i - 1] = n >>> 29,
o
}(e),
d = 1732584193,
h = 4023233417,
g = 2562383102,
m = 271733878,
n = 0; n < t.length; n += 16)
r = d,
l = h,
p = g,
f = m,
d = o(d, h, g, m, t[n + 0], 7, 3614090360),
m = o(m, d, h, g, t[n + 1], 12, 3905402710),
g = o(g, m, d, h, t[n + 2], 17, 606105819),
h = o(h, g, m, d, t[n + 3], 22, 3250441966),
d = o(d, h, g, m, t[n + 4], 7, 4118548399),
m = o(m, d, h, g, t[n + 5], 12, 1200080426),
g = o(g, m, d, h, t[n + 6], 17, 2821735955),
h = o(h, g, m, d, t[n + 7], 22, 4249261313),
d = o(d, h, g, m, t[n + 8], 7, 1770035416),
m = o(m, d, h, g, t[n + 9], 12, 2336552879),
g = o(g, m, d, h, t[n + 10], 17, 4294925233),
h = o(h, g, m, d, t[n + 11], 22, 2304563134),
d = o(d, h, g, m, t[n + 12], 7, 1804603682),
m = o(m, d, h, g, t[n + 13], 12, 4254626195),
g = o(g, m, d, h, t[n + 14], 17, 2792965006),
h = o(h, g, m, d, t[n + 15], 22, 1236535329),
d = a(d, h, g, m, t[n + 1], 5, 4129170786),
m = a(m, d, h, g, t[n + 6], 9, 3225465664),
g = a(g, m, d, h, t[n + 11], 14, 643717713),
h = a(h, g, m, d, t[n + 0], 20, 3921069994),
d = a(d, h, g, m, t[n + 5], 5, 3593408605),
m = a(m, d, h, g, t[n + 10], 9, 38016083),
g = a(g, m, d, h, t[n + 15], 14, 3634488961),
h = a(h, g, m, d, t[n + 4], 20, 3889429448),
d = a(d, h, g, m, t[n + 9], 5, 568446438),
m = a(m, d, h, g, t[n + 14], 9, 3275163606),
g = a(g, m, d, h, t[n + 3], 14, 4107603335),
h = a(h, g, m, d, t[n + 8], 20, 1163531501),
d = a(d, h, g, m, t[n + 13], 5, 2850285829),
m = a(m, d, h, g, t[n + 2], 9, 4243563512),
g = a(g, m, d, h, t[n + 7], 14, 1735328473),
h = a(h, g, m, d, t[n + 12], 20, 2368359562),
d = s(d, h, g, m, t[n + 5], 4, 4294588738),
m = s(m, d, h, g, t[n + 8], 11, 2272392833),
g = s(g, m, d, h, t[n + 11], 16, 1839030562),
h = s(h, g, m, d, t[n + 14], 23, 4259657740),
d = s(d, h, g, m, t[n + 1], 4, 2763975236),
m = s(m, d, h, g, t[n + 4], 11, 1272893353),
g = s(g, m, d, h, t[n + 7], 16, 4139469664),
h = s(h, g, m, d, t[n + 10], 23, 3200236656),
d = s(d, h, g, m, t[n + 13], 4, 681279174),
m = s(m, d, h, g, t[n + 0], 11, 3936430074),
g = s(g, m, d, h, t[n + 3], 16, 3572445317),
h = s(h, g, m, d, t[n + 6], 23, 76029189),
d = s(d, h, g, m, t[n + 9], 4, 3654602809),
m = s(m, d, h, g, t[n + 12], 11, 3873151461),
g = s(g, m, d, h, t[n + 15], 16, 530742520),
h = s(h, g, m, d, t[n + 2], 23, 3299628645),
d = c(d, h, g, m, t[n + 0], 6, 4096336452),
m = c(m, d, h, g, t[n + 7], 10, 1126891415),
g = c(g, m, d, h, t[n + 14], 15, 2878612391),
h = c(h, g, m, d, t[n + 5], 21, 4237533241),
d = c(d, h, g, m, t[n + 12], 6, 1700485571),
m = c(m, d, h, g, t[n + 3], 10, 2399980690),
g = c(g, m, d, h, t[n + 10], 15, 4293915773),
h = c(h, g, m, d, t[n + 1], 21, 2240044497),
d = c(d, h, g, m, t[n + 8], 6, 1873313359),
m = c(m, d, h, g, t[n + 15], 10, 4264355552),
g = c(g, m, d, h, t[n + 6], 15, 2734768916),
h = c(h, g, m, d, t[n + 13], 21, 1309151649),
d = c(d, h, g, m, t[n + 4], 6, 4149444226),
m = c(m, d, h, g, t[n + 11], 10, 3174756917),
g = c(g, m, d, h, t[n + 2], 15, 718787259),
h = c(h, g, m, d, t[n + 9], 21, 3951481745),
d = i(d, r),
h = i(h, l),
g = i(g, p),
m = i(m, f);
return (u(d) + u(h) + u(g) + u(m)).toLowerCase()
})
至此,加密入口已暴露出來了,即方法d
。
0x04 驗證加密方法可用性
sign簽名方法已經拿到手,接下來當然是要驗證它是否可用。
在Sources(原始碼)
選項卡左側的Snippets(程式碼片段)
Tab頁中新增一個程式碼片段,將sign簽名方法複製到裡面將其儲存,並且複製將4個關鍵引數傳入到方法中,然後點選執行(Ctrl+Enter)
。
重新傳送一個請求,然後將H5端真實傳送的四個引數的值分別複製到自定義引數中,檢視H5端生成的sign值是否與自己新建的程式碼片段執行的值是一致的,若是一致的則表示該sign簽名函式可被使用。
可以看見飛豬H5端產生的sign值與自定義產生的sign值是一致的,都是fd232f8836d8713c277215d71f43820c
,說明我們找到的sign簽名加密函式是正確的。
0x05 作者的一些話
該篇講述如何使用瀏覽器F12開發者工具
對一個加密函式的定位與尋找思路,作者強烈反對大家使用該技術進行實質性地爬蟲以及其他形式的利用。
請時刻牢記:誰把法律當兒戲,誰就必然亡於法律。