五百丁登錄enPassword參數

難度:★☆☆☆☆ 1星

一、目標

目標網站: https://www.500d.me/login/
登錄的時候提交表單裏密碼字段enPassword是被加密的:
0
本次目標就是破解這個參數加密。

二、分析

打開登錄頁:
https://www.500d.me/login/
打開開發者工具,切換到Network,清空掉無關請求,然後頁面裏輸入賬號密碼嘗試登陸,注意賬號密碼是隨便輸的,是故意讓它登陸失敗觀察一下流程的:
1
捕捉到了三個請求,先看下第一個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也是可以訪問的:
2
好的繼續看第二個Post請求:
https://www.500d.me/login/submit/
這個是實際提交登錄參數的,因此提交了一個表單有用戶名密碼之類的參數:
3
第三個請求實際上是一個雪碧圖,用於在頁面上顯示圖標用的,這裏不再詳述。
通過觀察請求大致捋出來了登錄的流程,先是發送一個請求獲取公鑰,然後再用js加密密碼提交登錄表單,接下來的重點就在第二個請求發送前的js邏輯,接下就是想辦法去定位到那段js代碼,複製第二個請求的url,打一個xhr斷點:
4
然後在頁面上重新嘗試登錄,就卡在了斷點這裏,格式化代碼,同時在調用棧裏往前回溯尋找相關的棧幀:
5
在success方法的棧幀裏看到了發出登錄請求的代碼,這個success是前面那個獲取公鑰的接口成功時的回調方法:
6
密碼字段加密的核心邏輯:
var rsaKey = new RSAKey();
rsaKey.setPublic(b64tohex(data.modulus), b64tohex(data.exponent));
var enPassword = hex2b64(rsaKey.encrypt(form.find("input[name='password']").val()));
然後把鼠標放到RSAKey上懸停一會兒,會彈出彈窗表明出處,單擊跟進入:
7
然後定位到了一個叫做rsa.js的文件,把這個文件整個摳出來新建一個文件encrypt.js放進去:
8
然後回到加密的方法,如法炮製找到b64tohex和hex2b64的邏輯:
9
10
這兩個都是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中:
11
都扣完了還是報錯:
12
然後補環境,把下面的代碼插入到encrypt.js的最前面:
// 補環境
const window = {
    navigator: {
        appName: "Netscape"
    }
};
const navigator = window.navigator;
然後再運行一下,能夠加密出密碼了:
13
把893行的測試代碼註釋掉,拷到pycharm裏,接下來會在python中調用這個js加密密碼。
這裏還有一個需要注意的問題,就是在登錄的時候提交登錄表單的接口必須帶上token請求頭,如果不帶的話就會405(方法不被允許: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status/405 ),使用ModHeader手動搞掉token試下:
14
可以看到405了:
15
這個token是用jQuery的ajaxSend方法設置的一個hook裏設置到請求頭上的,jQuery的ajaxSend可以設置一個函數在ajax發送之前運行,通過正則搜索“header.{0,100}token”,表示搜索出現在header附近的token,因爲js設置的話有可能就是這樣設置上的:
16
於是就定位到了這個文件:
https://static.500d.me/resources/500d/js/utils.js?v=V7119
文件的這個位置,就是在這裏設置的請求頭:
17
這個名爲token的cookie是在第一次訪問登錄頁的時候設置的cookie:
18
訪問一次登錄頁拿到這個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")


倉庫:

https://github.com/CC11001100/misc-crawler-public/tree/master/001-anti-crawler-js-re/01-005-www.500d.me


請注意爬蟲文章具有時效性,本文寫於2020-11-17日。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章