webpack-js-前端簽名(sign)算法實戰分析
一、簡介
目前許多網站前端源碼都趨于模塊化,參數值通過webpack的模塊進行加解密,這對于爬蟲和滲透都是一個攔路虎,因此寫下該博客,記錄自己的學習思路。
二、目標站點
這里分析的是某動某站前端的js代碼
http://www.xxx.com/
三、算法分析
3.1 正常發送請求包
打開開發者工具,點擊network,輸入賬號密碼,點擊登錄。

查看響應請求的內容,其中password的參數值被加密了,這里我輸入的是123456,密文看著很像md5,但是通過解密發現,密文又似乎不是md5。

發送第二個登錄包。
password的值沒變,但是sign的值發生了變化,較大幾率sign值在生成的時候,使用了時間戳。

此時尋找該參數加密點。
3.2 尋找password、sign加密相關的模塊與函數
全局搜索該登錄請求的接口

并成功找到相關的參數。

3.2.1 分析password
通過分析發現,password的值是由n決定的。
s = {
phone: e.phone,
password: n,
type: t,
sign: "",
timestamp: d
}
并在第1886行,找到了有關n的聲明

此時發現password被md5加密后,與pwdkey的值,進行字符串拼接后,在進行一次md5加密。
直接搜索pwdkey,并在第27589行,找到了該值,發現該值是不變的。

這里使用python3,寫一個簡單的password的加密demo。
import hashlib
def md5Encode(str):
m = hashlib.md5(str.encode(encoding='utf-8'))
return m.hexdigest()
password = "123456"
pwdkey = "kla5ra8h9s"
encrypt_password = md5Encode(md5Encode(password)+pwdkey)
print(encrypt_password)

發現該值,與我在前端登錄框處輸入密碼123456后,經前端webpack模塊加密后的值是一樣的,說明前面的分析思路是正確的。

3.2.2 分析sign
sign的生成方式在第1866行進行的。
d.sign = this.$md5Sign(n)

sign的值是通過md5Sign進行加密后得到的,因此這里需要分析兩個點。分別是md5Sign的算法流程,以及l的值。
3.2.2.1 分析l
在第1858行發現
l = "" + e.phone + n + t + d
這里我下個斷點,傳值進來方便查看。

l的值是由手機號+密碼密文+1+時間戳生成的。
即l=phone+md5(md5(password)+pwdkey)+1+時間戳
3.2.2.2 分析md5Sign
此時分析md5Sign的算法。
通過斷點追蹤發現,md5Sign和函數mn()有關。

有點奇怪,為什么不應該是直接追到md5Sign的函數聲明去嗎?怎么會是mn()的聲明。
此時我直接搜索了mn()
發送mn的值又賦值給了md5Sign
因此此處mn和md5Sign可以理解是等價的關系。

此時來分析mn的函數流程。
sn = a("8237"),
cn = a.n(sn);
function mn(e) {
var i = "",
a = "6d9fkhj33rk8sa5fc";
return i = e + a,
i = cn()(i).toLowerCase(),
i
}

此處并設置斷點傳值。

并連續調試到下一步

此時分析可知
e = phone +password的密文+1+時間戳
e = 151123412349ac13f8bad58389388ecc1604eaed32011635925676
a = 6d9fkhj33rk8sa5fc
i = e + a
i = phone +password的密文 +1 + 時間戳 + 6d9fkhj33rk8sa5fc
即i = 151123412349ac13f8bad58389388ecc1604eaed320116359256766d9fkhj33rk8sa5fc
這里為了方便查看看,就將i最后的值拆分一下
phonemd5(md5(password)+pwdkey)1 時間戳a 151123412349ac13f8bad58389388ecc1604eaed3201 |
16359256766d9fkhj33rk8sa5fc
i的值再被cn函數加密,加密后的值,全部通過toLowerCase()轉換成小寫字母。
sn = a("8237");
cn = a.n(sn);
為了方便調試,這里我打開了編輯器,準備扣前端js代碼下來,本地執行調試了。
先把前面的流程進行整理,這里我把所有時間戳寫死了,方便等下觀察值是否一樣。
當時間戳為1635925676時,sign的值為32138108f605a5122cc6f6c1bc54c7b3。(之前的截圖)
var password = "9ac13f8bad58389388ecc1604eaed320"
var phone = "15112341234"
var timestamp = "1635925676"
var a = "6d9fkhj33rk8sa5fc"
var sn = a("8237");
var cn = a.n(sn);
var i = phone + password + '1' + timestamp + a;
var sign = cn()(i).toLowerCase();
console.log(sign)

此時只需要分析a

斷點跟進
在這里找到了a

此時分析確定了分發器

我們將這段代碼全部扣下來。
聲明一個flag,用于接收分發器模塊的值。

將l賦值給flag,并要把原先的a("8237")和a.n(sn)修改成flag("8237")和flag.n(sn)

運行之后,報錯。
提示window沒有被定義,這里就把window聲明一下。

var window = global;
將這個聲明放在最上面,和flag的聲明放在一起。
此時再次運行。報錯,提示call沒有被定義。原因是因此flag中的8237模塊沒有被調用。

這里全局搜索8237
在此js文件中找到了有關8237的定義

將這個js代碼全部復制至本地的2.js中,并在1.js中進行調用。
并聲明一下window

并再次運行

和預期值不一樣?猜測時間戳填錯了。
因此此處重新抓一個時間戳的數據包。
并修改timestamp的值

此時本地調試的結果,與前端的結果一致,因此該sign的簽名算法分析完成。
四、使用python3完成此次簽名算法
import hashlib
import time
def md5Encode(str):
m = hashlib.md5(str.encode(encoding='utf-8'))
return m.hexdigest()
if __name__ == "__main__":
phone = "15112341234"
password = "123456"
pwdkey = "kla5ra8h9s"
#timestamp = "1635927403"
timestamp = int(time.time())
a = "6d9fkhj33rk8sa5fc"
n = md5Encode(md5Encode(password)+pwdkey)
i = phone + n + "1" + str(timestamp) + a
print("時間戳是:"+str(timestamp))
print(md5Encode(i))

使用系統時間戳

五、總結
遇到前端加密不要慌,作為一個安服仔,應該直接干就完了,反正js代碼已經默認開源給你了,慢慢調試分析算法邏輯,并調用相應的模塊即可。
自主學習時,解決問題應有多樣性,盡量還是多增加自己的知識層面。
實際解決問題時,應考慮效率。
所以此處我提供了javascript和python3的兩種解決問題的方式方法。