【JS 逆向百例】HN某服務網登入逆向,驗證碼形同虛設

K哥爬蟲發表於2022-01-18

宣告

本文章中所有內容僅供學習交流,抓包內容、敏感網址、資料介面均已做脫敏處理,嚴禁用於商業用途和非法用途,否則由此產生的一切後果均與作者無關,若有侵權,請聯絡我立即刪除!

逆向目標

  • 目標:某政務服務網登入介面
  • 主頁:aHR0cHM6Ly9sb2dpbi5obnp3ZncuZ292LmNuL3RhY3MtdWMvbG9naW4vaW5kZXg=
  • 介面:aHR0cHM6Ly9sb2dpbi5obnp3ZncuZ292LmNuL3RhY3MtdWMvbmF0dXJhbE1hbi9sb2dpbk5v
  • 逆向引數:

    Form Data:loginNo、loginPwd、code、requestUUID

    Request Headers:token

抓包分析

本次逆向目標來源於某位粉絲的求助:

01.png

隨便輸入賬號密碼點選登陸,抓包發現介面的 Request Headers 有個加密引數 token,Form Data 裡 loginNo、loginPwd、code、requestUUID 都是經過加密處理了的,loginNo 和 loginPwd 應該就是使用者名稱密碼了,由於登入前需要過一下滑動驗證碼,因此可以猜測另外兩個引數與驗證碼有關,不過僅從抓包來看,另外兩個引數類似於 uuid 的格式,不太像驗證碼的引數。

02.png

03.png

另外可以注意到登陸前,有兩次 csrfSave 和一次 verCode 的請求,正常請求成功就會返回一個 JSON,裡面有個 data 引數,後面應該是會用到的。

04.png

引數逆向

Form Data

先看 Form Data,搜尋任意一個引數,比如 loginNo,很容易在 login.js 裡找到加密的地方,使用者名稱和密碼都經過了 encrypt 這個函式進行加密,backUrl 這個值,是利用 localStorage 屬性,從瀏覽器儲存的鍵值對的資料裡取的,為空也不影響。

05.png

跟進 encrypt,可以看到用到了 JSEncrypt,標準的 RSA 加密:

06.png

再看看 loginCode,直接搜尋這個值,可以看到是 verCode 這個請求返回的:

07.png

08.png

然後再看看 requestUUID,其值就是個 UUID,直接在當前檔案(login.js)裡搜尋,可以看到定義的地方,有個 uploadUUID() 方法,就是在設定 UUID 的值,方法裡面是向一個 uploadIdentifier 的介面傳送了 post 請求:

09.png

10.png

這裡注意,如果你直接全域性搜尋 UUID 的話,還可以在 common.js 裡搜尋到一個方法,經過測試,直接使用這個方法生成一個 uuid 也是可以請求通過的,這網站可能不嚴謹,不會嚴格檢測這個值。

11.png

Request Headers

Form Data 解決了,再來看看 Request Headers 裡的 token 引數,由於它存在於請求頭裡,所以我們可以通過 Hook 的方式來查詢其生成的地方:

(function () {
    var org = window.XMLHttpRequest.prototype.setRequestHeader;
    window.XMLHttpRequest.prototype.setRequestHeader = function (key, value) {
        if (key == 'token') {
            debugger;
        }
        return org.apply(this, arguments);
    };
})();

這裡我們也可以直接搜尋 token、setRequestHeader 之類的關鍵字,很容易在 common.js 裡找到,當我們點選登陸,會有一個 csrfSave 的請求,返回的 data 值,經過 encrypt 方法加密後就是登陸請求頭的 token 了。

這個 token 引數在很多請求中都會用到,生成方法是一樣的,都是拿 csrfSave 請求返回的 data 經過 RSA 加密後得到的:

12.png

另外注意一點的就是,以上所有涉及到網路請求的,Cookie 都需要一個 SESSION 值,這個可以在首次訪問頁面獲取到:

13.png

登陸流程

這裡我們理一下登陸的流程:

  1. 訪問首頁拿 Cookie 中的 SESSION 值;
  2. 訪問 csrfSave,拿到一個 data 值,經過 RSA 加密得到 token,攜帶 token 訪問 uploadIdentifier,拿到 uuid;
  3. 訪問 csrfSave,拿到一個 data 值,經過 RSA 加密得到 token,攜帶 token 訪問 verCode,拿到 code;
  4. 訪問 csrfSave,拿到一個 data 值,經過 RSA 加密得到 token,攜帶 token、uuid、code 和加密後的賬號密碼,訪問 loginNo 登入。

這裡第2步,也可以直接用 Python 或者 JS 生成一個 uuid,網站校驗不嚴格,也可以通過,另外可以看出這個滑塊是假的,通過程式碼可以無視滑塊進行登入。

14.png

完整程式碼

GitHub 關注 K 哥爬蟲,持續分享爬蟲相關程式碼!歡迎 star !https://github.com/kgepachong/

以下只演示部分關鍵程式碼,不能直接執行! 完整程式碼倉庫地址:https://github.com/kgepachong...

JavaScript 加密程式碼

/* ==================================
# @Time    : 2022-01-11
# @Author  : 微信公眾號:K哥爬蟲
# @FileName: encrypt.js
# @Software: PyCharm
# ================================== */

JSEncrypt = require("jsencrypt")

function encrypt(pwd){
    var key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsgDq4OqxuEisnk2F0EJFmw4xKa5IrcqEYHvqxPs2CHEg2kolhfWA2SjNuGAHxyDDE5MLtOvzuXjBx/5YJtc9zj2xR/0moesS+Vi/xtG1tkVaTCba+TV+Y5C61iyr3FGqr+KOD4/XECu0Xky1W9ZmmaFADmZi7+6gO9wjgVpU9aLcBcw/loHOeJrCqjp7pA98hRJRY+MML8MK15mnC4ebooOva+mJlstW6t/1lghR8WNV8cocxgcHHuXBxgns2MlACQbSdJ8c6Z3RQeRZBzyjfey6JCCfbEKouVrWIUuPphBL3OANfgp0B+QG31bapvePTfXU48TYK0M5kE+8LgbbWQIDAQAB";
    var encrypt = new JSEncrypt();
    encrypt.setPublicKey(key);
    var encrypted = encrypt.encrypt(pwd);
    return encrypted;
}

// 測試樣例
// console.log(encrypt("15555555555"))

Python 登入程式碼

# ==================================
# @Time    : 2022-01-11
# @Author  : 微信公眾號:K哥爬蟲
# @FileName: hnzww_login.py
# @Software: PyCharm
# ==================================


import execjs
import requests


cookies = {}
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"

with open("encrypt.js", encoding="utf-8") as f:
    js = execjs.compile(f.read())


def csrf_save():
    url = "脫敏處理,完整程式碼關注 GitHub:https://github.com/kgepachong/crawler"
    headers = {"User-Agent": UA}
    response = requests.post(url=url, headers=headers, cookies=cookies).json()
    data = response["data"]
    return data


def get_session():
    url = "脫敏處理,完整程式碼關注 GitHub:https://github.com/kgepachong/crawler"
    headers = {"User-Agent": UA}
    response = requests.get(url=url, headers=headers)
    cookies.update(response.cookies.get_dict())


def get_uuid():
    url = "脫敏處理,完整程式碼關注 GitHub:https://github.com/kgepachong/crawler"
    headers = {
        "User-Agent": UA,
        "token": js.call("encrypt", csrf_save())
    }
    response = requests.post(url=url, headers=headers, cookies=cookies).json()
    uuid = response["data"]
    return uuid


def ver_code():
    url = "脫敏處理,完整程式碼關注 GitHub:https://github.com/kgepachong/crawler"
    headers = {
        "User-Agent": UA,
        "token": js.call("encrypt", csrf_save())
    }
    response = requests.post(url=url, headers=headers, cookies=cookies).json()
    data = response["data"]
    return data


def login(phone, pwd, code, uuid):
    url = "脫敏處理,完整程式碼關注 GitHub:https://github.com/kgepachong/crawler"
    headers = {
        "User-Agent": UA,
        "token": js.call("encrypt", csrf_save())
    }
    data = {
        "backUrl": "",
        "loginNo": js.call("encrypt", phone),
        "loginPwd": js.call("encrypt", pwd),
        "code": code,
        "requestUUID": uuid,
        "guoBanAuthCode": ""
    }
    response = requests.post(url=url, headers=headers, cookies=cookies, data=data)
    print(response.json())


def main():
    phone = input("請輸入賬號:")
    pwd = input("請輸入密碼:")
    get_session()
    uuid = get_uuid()
    code = ver_code()
    login(phone, pwd, code, uuid)


if __name__ == '__main__':
    main()

相關文章