大家好,我是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
-
tls指纹
总结:经过分析,我们可以确认该网站不需要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指纹?这是一个黑客大佬总结的几种方法:
-
代理中转请求
-
使用Go语言爬虫库
-
魔改requests
-
访问ip指定host绕过waf
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设置代理发包
我们对GoLang、Python两次发包后的请求参数对比分析,截图如下所示:
-
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捂手特征代码通过,难度较大的就不能通过了,还需要新的方案去替代哦。这里突出一点,学无止境啊^_^⛽️
本篇分享到这里就结束了,欢迎大家关注下一期,我们不见不散☀️☀️😊
关注我们获得更多精彩内容
gzh:逆向与爬虫的故事
专注于网络爬虫、JS逆向、APP逆向、安全攻防实战经验分享及总结!
30篇原创内容
我是TheWeiJun,有着执着的追求,信奉终身成长,不定义自己,热爱技术但不拘泥于技术,爱好分享,喜欢读书和乐于结交朋友,欢迎加我微信与我交朋友。
分享日常学习中关于爬虫、逆向和分析的一些思路,文中若有错误的地方,欢迎大家多多交流指正☀️