難度:★☆☆☆☆ 1星
一、目標
目標網站: https://www.500d.me/login/
登錄的時候提交表單裏密碼字段enPassword是被加密的:
本次目標就是破解這個參數加密。
二、分析
打開登錄頁:
https://www.500d.me/login/
打開開發者工具,切換到Network,清空掉無關請求,然後頁面裏輸入賬號密碼嘗試登陸,注意賬號密碼是隨便輸的,是故意讓它登陸失敗觀察一下流程的:
捕捉到了三個請求,先看下第一個Get請求,鏈接是:
https://www.500d.me/common/public_key/?_=1605590883257
響應內容:
{ "modulus": "AM+emhhTb5EOH/ZbDg78dHOw79H4aFQkF4pCFCw9yo8oRigsa0p6bIB3UVjK+S5E1v3OSy1+/4WM10Zb+k+qV/7hK0GuoO2w15s+0nYJLjPC4SO8WmFgNC5aQsdHOPXt9hcK6sbJKh4dWR/U/pyfTOlp1IJqx4ZALyAf5sZN25Np", "exponent": "AQAB" }乍一看有點懵逼,其實如果之前搞過類似的話看一眼url裏面的public_key和響應內容,大概就知道這應該是就是獲取加密密碼時使用的公鑰了,密碼也可能就是rsa加密的,我們藉助ModHeader來測試一下這個請求是否對cookie或者referer做了檢查,可以看到不帶cookie和referer也是可以訪問的:
好的繼續看第二個Post請求:
https://www.500d.me/login/submit/
這個是實際提交登錄參數的,因此提交了一個表單有用戶名密碼之類的參數:
第三個請求實際上是一個雪碧圖,用於在頁面上顯示圖標用的,這裏不再詳述。
通過觀察請求大致捋出來了登錄的流程,先是發送一個請求獲取公鑰,然後再用js加密密碼提交登錄表單,接下來的重點就在第二個請求發送前的js邏輯,接下就是想辦法去定位到那段js代碼,複製第二個請求的url,打一個xhr斷點:
然後在頁面上重新嘗試登錄,就卡在了斷點這裏,格式化代碼,同時在調用棧裏往前回溯尋找相關的棧幀:
在success方法的棧幀裏看到了發出登錄請求的代碼,這個success是前面那個獲取公鑰的接口成功時的回調方法:
密碼字段加密的核心邏輯:
var rsaKey = new RSAKey(); rsaKey.setPublic(b64tohex(data.modulus), b64tohex(data.exponent)); var enPassword = hex2b64(rsaKey.encrypt(form.find("input[name='password']").val()));然後把鼠標放到RSAKey上懸停一會兒,會彈出彈窗表明出處,單擊跟進入:
然後定位到了一個叫做rsa.js的文件,把這個文件整個摳出來新建一個文件encrypt.js放進去:
然後回到加密的方法,如法炮製找到b64tohex和hex2b64的邏輯:
這兩個都是base64.js文件中,同樣跟進去,然後整個摳出來放到encrypt.js,然後在encrypt.js中嘗試寫一個加密密碼的方法爲外部提供調用的接口:
/** * 向外界暴露加密密碼的方法 * * @param passwd * @param modulus * @param exponent * @returns {string|*} */ function encryptPasswd(passwd, modulus, exponent) { const rsaKey = new RSAKey(); rsaKey.setPublic(b64tohex(modulus), b64tohex(exponent)); return hex2b64(rsaKey.encrypt(passwd)); } console.log(encryptPasswd("cc11001100", "{\n" + " \"modulus\": \"AJbFLrvha10BPOdevQ+cuIDirMylI9srBg3MQe/3jG3FovKT3+/hSHPZbJljaOHnLHskJh1+r8ECwpJEU16xA73D+SbCcK83my+vMH2VLdP9w6eRfqkEfo+W/5yn7ZmNAnGTPlTC29I3b8cyVEuUHHO8HQGpgJXJp7FDbTjxgj6R\"\n" + "}", "AQAB"));同時node運行測試一下是否OK,然後發現報錯了,因爲扣的js不全,有些依賴沒有放到encrypt.js中,回到登錄頁面,查看源代碼,搜索rsa定位到這裏,把紅色方框內沒有扣的js一股腦兒扣了放到encrypt.js中:
都扣完了還是報錯:
然後補環境,把下面的代碼插入到encrypt.js的最前面:
// 補環境 const window = { navigator: { appName: "Netscape" } }; const navigator = window.navigator;然後再運行一下,能夠加密出密碼了:
把893行的測試代碼註釋掉,拷到pycharm裏,接下來會在python中調用這個js加密密碼。
這裏還有一個需要注意的問題,就是在登錄的時候提交登錄表單的接口必須帶上token請求頭,如果不帶的話就會405(方法不被允許: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/405 ),使用ModHeader手動搞掉token試下:
可以看到405了:
這個token是用jQuery的ajaxSend方法設置的一個hook裏設置到請求頭上的,jQuery的ajaxSend可以設置一個函數在ajax發送之前運行,通過正則搜索“header.{0,100}token”,表示搜索出現在header附近的token,因爲js設置的話有可能就是這樣設置上的:
於是就定位到了這個文件:
https://static.500d.me/resources/500d/js/utils.js?v=V7119
文件的這個位置,就是在這裏設置的請求頭:
這個名爲token的cookie是在第一次訪問登錄頁的時候設置的cookie:
訪問一次登錄頁拿到這個token就好。
流程基本捋清楚了,接下來是編碼實現。
三、編碼實現
#!/usr/bin/env python3 # encoding: utf-8 """ @author: CC11001100 """ import functools import time import execjs import requests session = requests.session() def login(username, passwd): token = get_token() print(f"token = {token}") public_key = get_public_key() print(f"public key = {public_key}") data = { "username": username, "enPassword": load_js_context().call("encryptPasswd", passwd, public_key["modulus"], public_key["exponent"]), "service": "", "remember": True } print(data) headers = { # 這個參數是必須要帶的,什麼鬼情況,是後端的框架要檢測還是手動做的檢測,好像有些框架需要這個參數知道是個xhr請求 "X-Requested-With": "XMLHttpRequest", # U-A反倒不是必須的... "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36", # token是必須要帶的 "token": token, } url = "https://www.500d.me/login/submit/" r = session.post(url, data=data, headers=headers) print(r.status_code) # 200 print(r.text) # {"type":"success","content":""} @functools.lru_cache(maxsize=1) def load_js_context(): with open("./encrypt.js", encoding="UTF-8") as f: js_code = f.read() return execjs.compile(js_code) def get_token(): url = "https://www.500d.me/login/" return session.get(url).cookies["token"] def get_public_key(): url = f"https://www.500d.me/common/public_key/?_={int(time.time() * 1000)}" return session.get(url).json() if __name__ == "__main__": # 註冊資料: # 郵箱: [email protected] # 暱稱: cc11001100_test # 密碼: ccIs0KccIs0K login("[email protected]", "ccIs0KccIs0K")
倉庫:
請注意爬蟲文章具有時效性,本文寫於2020-11-17日。