某某網站 JS 逆向及 tls 指紋繞過分析

      大家好,我是 TheWeiJun;光陰似箭、日月如梭,突然發現又有好長時間沒有更新了。還好總有粉絲朋友找我提問,今天更新一篇粉絲 Robbers 提到的網站問題,主要涉及 js 逆向和 tls 指紋繞過。歡迎各位讀者朋友多多閱讀與交流!

趣味模塊

       Robbers 是一名 spider 工程師,最近 Robbers 遇到了一個棘手的問題:Robbers 在訪問某某網站時,遇到了 JS 加密參數。Robbers 憑藉自己超高的專業技能對該加密參數逆向還原後,用 requests、httpx、aiohttp 等包去發包,居然認證不通過,提示身份授權失敗。這篇文章,我們將和 Robbers 一起並肩作戰,去解決這個問題!

一、前言介紹

      我們在以往的文章中都是提到了如何從 params、data、headers、cookies、response 中去還原加密參數,通過還原加密參數的方式即可實現數據採集。而今天我們要分享的文章中,和提到的這幾個類型完全沒有任何關聯,遇到這樣的問題,該如何解決這類型的問題?帶着這些疑問耐心看完本篇文章,你就豁然開朗了!

二、參數分析

1、首先打開我們今天要模擬的網站,刷新當前頁面,截圖如下:

2、打開開發者工具 DevTools, 選擇 Network 欄目, 刷新當前頁面,截圖如下:

3、經過分析可以確定該接口即爲我們要獲取數據的地址,接下來我們進行參數分析:

Request 參數分析:

總結: 該接口都是明文,不需要進行任何還原。

Headers 參數分析:

總結: u-sign 目測爲 md5 算法加密參數。

通過分析,我們可以確定 u-sign 參數是被加密處理了。經過重放請求包,不能夠缺少 u-sign 參數,接下來我們需要進入 JS 段點調試分析加密參數環節一探究竟了。

三、斷點調試

1、使用 XHR/fetch 打上斷點,當該請求發包的時候,捕獲斷點如下:

2、在 Call Stack 欄目中追溯 headers 參數堆棧,截圖如下所示:

3、在 u-sign 參數下面一行打上斷點,查看 u-sign 對應的 value 的值,截圖如下:

4、確定加密函數爲 i 後,我們進入該函數,查看加密邏輯,截圖如下:

5、確定 n(a) 爲 u-sign 參數的值後,我們在 console 輸出 a 參數,copy(a) 用前面提到的 md5 去校驗看是否等於該參數的值,截圖如下:

總結: 確定參數算法及整個流程貫通後,接下來,我們只需要對 js 代碼加密算法進行還原即可。

四、算法還原

1、Python 版本算法還原代碼如下:

# -*- coding: utf-8 -*-
# --------------------------------------
# @author : 逆向與爬蟲故事公衆號
# --------------------------------------
import json
import hashlib
def get_sign(data: dict) -> str:
    data_str = json.dumps(data, separators=(',', ':'))
    text = f"{data_str.lower()}&9sasji5owng41irkisvtjhlxhmrysrp1"
    hash_md5 = hashlib.md5()
    hash_md5.update(text.encode())
    sign = hash_md5.hexdigest()
    return sign
if __name__ == '__main__':
    headers = {
        "Accept-Encoding": "gzip, deflate, br",
        "Connection": "keep-alive",
        'Accept': '*/*',
        'Accept-Language': 'zh-CN,zh;q=0.9',
        'Cache-Control': 'no-cache',
        "Content-Length": "132",
        'Pragma': 'no-cache',
        'Sec-Fetch-Dest': 'empty',
        'Sec-Fetch-Mode': 'cors',
        'Sec-Fetch-Site': 'same-site',
        'sec-ch-ua': '"Chromium";v="106", "Google Chrome";v="106", "Not;A=Brand";v="99"',
        'sec-ch-ua-mobile': '?0',
        'sec-ch-ua-platform': '"macOS"',
        'u-sign': '37635b63e0f1973c61b1444983eab1be',
        'u-token': '',
    }
    json_data = {
        'keyword': '',
        'provinceNames': [],
        'natureTypes': [],
        'eduLevel': '',
        'categories': [],
        'features': [],
        'pageIndex': 5,
        'pageSize': 20,
        'sort': 11,
    }
    sign = get_sign(json_data)
    print(sign)
    print(headers['u-sign'])

2、代碼運行後,Pycharm 打印結果如下:

3、算法還原後,使用 Python 發送請求包,截圖如下:

總結: 參數完全一致的情況無法通過認證,接下來我們進入新的環節解決這個問題吧!

五、指紋繞過

1、在我們參數算法完全還原的情況,請求該網站卻提示身份認證失敗,我們重新梳理下可能存在的情況如下:

總結: 經過分析,我們可以確認該網站不需要 cookies,故第一種懷疑排除掉;接下來進行 http2.0 驗證。

2、可能比較傻,我確實用 httpx 驗證了下該網站,截圖如下:

with httpx.Client(http2=True) as req:
    response = req.post('https://xxxxxxxx.cn/xxxxx.basiclib.api.college.query',
                        headers=headers, json=json_data)
    print(response.text)

Pycharm 代碼運行後,截圖如下:

總結: 結局總是那麼不理想,此刻懷疑的方案只剩下最後一種:該網站對 tls 請求指紋進行驗證;接下來我們繼續分析。

可能到這裏會有人問,什麼是 tls 指紋?

TLS 指紋,也有人叫 JA3 指紋。在創建 TLS 連接時,根據 TLS 協議在 Client Hello 階段發送的數據包就是就是 TLS 指紋。不同瀏覽器、不同版本(不同框架)因爲對協議的理解和應用不一樣,所以發送的數據包內容也就不一樣,所以就形成了 TLS 指紋。

3、使用 Postman 進行發包測試,不再使用 Python 第三方包,截圖如下所示:

總結: 看到此處後一下豁然開朗了,可以肯定對方服務端會對請求指紋進行校驗,如果是我們剛剛使用的第三方包,都會被服務端給識別到,最後返回身份授權失敗錯誤。那麼我們如何過 TLS 指紋呢?

怎麼過 TLS 指紋?這是一個黑客大佬總結的幾種方法:

4、接下來,我們使用 Golang 編寫代碼及還原算法如下:

// Package main -----------------------------
// @author    : 逆向與爬蟲的故事
// -------------------------------------------
package main
import (
  "crypto/md5"
  "fmt"
  "io/ioutil"
  "log"
  "net/http"
  "strings"
  "time"
)
func main() {
  client := &http.Client{}
  dataStr := `{"keyword":"","provinceNames":[],"natureTypes":[],"eduLevel":"","categories":[],"features":[],"pageIndex":2,"pageSize":20,"sort":11}`
  var data = strings.NewReader(dataStr)
  req, err := http.NewRequest("POST", "https://xxxxxx/xxxxx.query", data)
  if err != nil {
    log.Fatal(err)
  }
  sign := fmt.Sprintf("%x", md5.Sum([]byte(strings.ToLower(dataStr)+"&9sasji5owng41irkisvtjhlxhmrysrp1")))
  fmt.Println(sign)
  req.Header.Set("Accept", "*/*")
  req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
  req.Header.Set("Connection", "keep-alive")
  req.Header.Set("Content-Type", "application/json")
  req.Header.Set("Origin", "https://xxxxx.cn")
  req.Header.Set("Referer", "https://xxxxxx.cn/")
  req.Header.Set("Sec-Fetch-Dest", "empty")
  req.Header.Set("Sec-Fetch-Mode", "cors")
  req.Header.Set("Sec-Fetch-Site", "same-site")
  req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36")
  req.Header.Set("sec-ch-ua", `"Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"`)
  req.Header.Set("sec-ch-ua-mobile", "?0")
  req.Header.Set("sec-ch-ua-platform", `"Windows"`)
  req.Header.Set("u-sign", sign)
  req.Header.Set("u-token", "")
  resp, err := client.Do(req)
  if err != nil {
    log.Fatal(err)
  }
  defer resp.Body.Close()
  bodyText, err := ioutil.ReadAll(resp.Body)
  if err != nil {
    log.Fatal(err)
  }
  fmt.Printf("%s\n", bodyText)
  fmt.Println(string(time.Now().Weekday()))
}

5、執行編輯好的代碼,GoLand 打印如下:

總結: 到此我們已經能夠解決 Robbers 粉絲遇到的問題了,這也讓我意識到隨着反爬策略的升級,服務端可能會對爬蟲最常用的第三方包進行請求指紋檢測。同時也說明了,爬蟲除了 Python,用 Go 其實也是一個不錯的選擇。

六、學習展望

筆者對 GoLang、Python 代碼分別設置代理後,用 charles 抓包分析如下:

我們對 GoLang、Python 兩次發包後的請求參數對比分析,截圖如下所示:

總結: 本來想使用 charles 攔截請求包查看數據包有哪些區別,但是感覺使用 charles 查看不夠直觀,charles 應用中我們也得到了一些有用信息 (標紅部分),提取服務器的 ip+port 使用 Wireshark 打開查看,看看有沒有更直觀的信息吧。

使用 Wireshark 抓包,再次使用 go、Python 去發包,發包後根據 charles 獲取的 ip 信息篩選 tls 指紋相關數據包,截圖如下所示:

緊接着我們點擊如下按鈕進行參數定位:

將鼠標拉到最後,可以看到 tls 也就是 JA3 指紋如下所示:

整理 go、python 發包後的指紋文本對比如下:

總結: 從上圖可以看出兩個請求包的 JA3 指紋加密算法不一致;如果我們還要繼續使用 Python requests 去實現代碼,可以嘗試使用魔改 requests 修改 TLS 握手特徵的代碼去實現,也可以去閱讀下 tls 指紋相關的文檔。簡單的網站是可以通過使用魔改 requests 修改 TLS 捂手特徵代碼通過,難度較大的就不能通過了,還需要新的方案去替代哦。這裏突出一點,學無止境啊 ^_^⛽️

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/p5di2mWVLut7Grv4TDQvkg