freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

爬虫技巧-TLS指纹认证绕过(详细讲解)
thundersword 2023-01-10 18:31:40 293917
所属地 河南省

0x1.基本说明

1.简单介绍

英文关键词叫 tls fingerprint ,这个网站 https://tlsfingerprint.io/提供了比较详细的介绍。

简单来说,不同的 TLS implementation (浏览器 / 各种语言的 HTTP 库)在建立 TLS 连接的时候发送的信息(支持的密码学套件 / 签名算法之类的)不一样。如果对面使用了某些流行的 HTTP 库,并且没有试图模仿浏览器的行为的话,你可以利用 tls fingerprint 把它和正常的浏览器区分开来。

比如目前 Chrome 的 tls fingerprint 是 e47eae8f8c4887b6 ,可以在这里查看详细信息 https://tlsfingerprint.io/id/e47eae8f8c4887b6

2.理解说明

这个就是我之前没了解过的技术了,这次在玩chatGPT才知道,没想到还可以通过验证tls的过程来检验是什么样的客户端从而拒绝服务,从大佬写的revChatGPT库中知道了服务器还可以这样屏蔽脚本,这样屏蔽之后你再使用Python等代码访问网站时无论怎么修改头部等信息都会访问失败,让你丈二和尚摸不着头脑,还好这里学到了。

不过此方法并不是无懈可击,我所知的python和go都有成型的http库来模拟不同浏览器的tls指纹进行访问的了。

然后我在看了这篇文章之后:使用Node.js绕过TLS指纹识别.md

对其大致有了些了解。

这篇文章中不仅讲解了tls指纹的基本原理和工作方法,而且提到了怎么用Nodejs来绕过一些网站简单的tls指纹的黑名单过滤爬虫的方法。

简单来讲,因为性能、安全和客户喜好等各种考虑,不同的浏览器客户端在进行TLS连接时采取的密码顺序、TLS扩展列表、客户偏好等等数据都会不相同。

对这些字符串进行哈希处理,会得到客户端在TLS握手时的唯一id值,这个id一般只和客户端有关,通常使用的特定算法称为JA3,在此处定义更精确。

我们可以通过这个网站来查询自己客户端的JA3指纹:JA3.ZONE | JA3 Fingerprint Database【好像只能浏览器查询,代码语言好像不能用这个网站查询】

总之,通过这个指纹,服务器就可以直接在传输层确定我们的客户端,不论你http的header是怎么改的。

由此就可以进行一些操作,屏蔽一些不合法的爬虫之类的。

比如可以用黑名单方式,把常见的代码客户端,如python、go、nodejs、curl等等的tls指纹屏蔽掉,识别到了则返回错误信息,这就是一般的黑名单屏蔽方式。

还可以比如说要求tls指纹在交互全程完全相同,这样在添加了crsftoken的情况下我们就不能通过交替使用浏览器模拟和代码模拟两种方式来运行爬虫了。

那么没有办法绕过这个判断吗?

当然有。

首先这个指纹并不是tls规定的,也就是说只是因为各种原因开发者在设计客户端浏览器时产生了不同的指纹,那么如果我们能控制整个tls访问过程,我们就可以伪装为一个浏览器。

这并不总是可以的,因为许多TLS实现可能不允许修改或者部分允许修改其中的一部分详细配置,包括NodeJS和Python,但是一些更底层的语言比如c和go语言可以。

详细方法见下方吧。

3.相关网站收集

收集TLS指纹的网站:https://tlsfingerprint.io/

查询自己客户端的JA3指纹:JA3.ZONE | JA3 Fingerprint Database【好像只能浏览器查询,代码语言好像不能用这个网站查询】

0x2.绕过方法

1.python完全模拟

1.1.Python-TLS-Client库

(1)下载安装

【可模拟任何浏览器】

【缺点是不能进行流操作,不能不阻塞下载文件】

python本身应该是不能操作tls中的具体配置的,但是比较有趣的是python可以运行静态库(dll、so)中的c函数,也就是说底层轮子有了python便有了这个功能。

有个大佬已经写好这个功能了,添加了各种平台的依赖库,支持各种平台,名为Python-TLS-Client,上线了pypi,安装方法:

pip install tls-client

它的函数语法吸纳了requests库,所以就很熟悉,唯一不一样的就是添加了一个模拟各种浏览器的tls指纹的参数client_identifier,只需要添加这个,服务器就不能通过tls指纹来区分python和浏览器。

(2)基本使用方法

基本使用方法如下:

(1)模拟特定的浏览器的tls指纹

import tls_client

# You can also use the following as `client_identifier`:
# Chrome --> chrome_103, chrome_104, chrome_105
# Firefox --> firefox_102, firefox_104
# Opera --> opera_89, opera_90
# Safari --> safari_15_3, safari_15_6_1, safari_16_0
# iOS --> safari_ios_15_5, safari_ios_15_6, safari_ios_16_0
# iPadOS --> safari_ios_15_6
session = tls_client.Session(
client_identifier="chrome_105"
)

res = session.get(
"https://www.example.com/",
headers={
   "key1": "value1",
},
proxy="http://user:password@host:port"
)

(2)直接设定各种详细参数

import tls_client

session = tls_client.Session(
ja3_string="771,4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-13-18-51-45-43-27-17513,29-23-24,0",
h2_settings={
   "HEADER_TABLE_SIZE": 65536,
   "MAX_CONCURRENT_STREAMS": 1000,
   "INITIAL_WINDOW_SIZE": 6291456,
   "MAX_HEADER_LIST_SIZE": 262144
},
h2_settings_order=[
   "HEADER_TABLE_SIZE",
   "MAX_CONCURRENT_STREAMS",
   "INITIAL_WINDOW_SIZE",
   "MAX_HEADER_LIST_SIZE"
],
supported_signature_algorithms=[
   "ECDSAWithP256AndSHA256",
   "PSSWithSHA256",
   "PKCS1WithSHA256",
   "ECDSAWithP384AndSHA384",
   "PSSWithSHA384",
   "PKCS1WithSHA384",
   "PSSWithSHA512",
   "PKCS1WithSHA512",
],
supported_versions=["GREASE", "1.3", "1.2"],
key_share_curves=["GREASE", "X25519"],
cert_compression_algo="brotli",
pseudo_header_order=[
   ":method",
   ":authority",
   ":scheme",
   ":path"
],
connection_flow=15663105,
header_order=[
   "accept",
   "user-agent",
   "accept-encoding",
   "accept-language"
]
)
res = session.post(
"https://www.example.com/",
headers={
   "key1": "value1",
},
json={
   "key1": "key2"
}
)

(3)注意要点

python本身是不能直接设置tls相关参数的,它是调用了c的库才可以,所以这个包中有很多不同平台的静态库。

如果你想要用Pyinstaller或Pyarmor将此库进行打包的话需要添加上对应的静态库依赖,方法如下:

Linux - Ubuntu / x86:

--add-binary '{path_to_library}/tls_client/dependencies/tls-client-x86.so:tls_client/dependencies'

Linux Alpine / AMD64:

--add-binary '{path_to_library}/tls_client/dependencies/tls-client-amd64.so:tls_client/dependencies'

MacOS M1 and older:

--add-binary '{path_to_library}/tls_client/dependencies/tls-client-x86.dylib:tls_client/dependencies'

MacOS M2:

--add-binary '{path_to_library}/tls_client/dependencies/tls-client-arm64.dylib:tls_client/dependencies'

Windows:

--add-binary '{path_to_library}/tls_client/dependencies/tls-client.dll;tls_client/dependencies'

2.golang完全模拟

2.1.TLS-Client库

(1)下载安装

开源项目:https://github.com/bogdanfinn/tls-client

(2)基本使用方法

模拟Chrome-105。

package main

import (
"fmt"
"io"
"log"

http "github.com/bogdanfinn/fhttp"
tls_client "github.com/bogdanfinn/tls-client"
)

func main() {
jar := tls_client.NewCookieJar()
options := []tls_client.HttpClientOption{
tls_client.WithTimeoutSeconds(30),
tls_client.WithClientProfile(tls_client.Chrome_105),
tls_client.WithNotFollowRedirects(),
tls_client.WithCookieJar(jar), // create cookieJar instance and pass it as argument
//tls_client.WithProxyUrl("http://user:pass@host:port"),
//tls_client.WithInsecureSkipVerify(),
}

client, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
if err != nil {
log.Println(err)
return
}

req, err := http.NewRequest(http.MethodGet, "https://tls.peet.ws/api/all", nil)
if err != nil {
log.Println(err)
return
}

req.Header = http.Header{
"accept":                   {"*/*"},
"accept-language":           {"de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"},
"user-agent":               {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36"},
http.HeaderOrderKey: {
"accept",
"accept-language",
"user-agent",
},
}

resp, err := client.Do(req)
if err != nil {
log.Println(err)
return
}

defer resp.Body.Close()

log.Println(fmt.Sprintf("status code: %d", resp.StatusCode))

readBytes, err := io.ReadAll(resp.Body)
if err != nil {
log.Println(err)
return
}

log.Println(string(readBytes))
}

也可以操作具体配置:

req, err := http.NewRequest(http.MethodGet, "https://tls.browserleaks.com/json", nil)
    if err != nil {
        log.Println(err)
        return
    }

    req.Header = http.Header{
        "accept":          {"*/*"},
        "accept-encoding": {"gzip"},
        "accept-language": {"de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"},
        "user-agent":      {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"},
        http.HeaderOrderKey: {
            "accept",
            "accept-encoding",
            "accept-language",
            "user-agent",
        },
    }

    resp, err := client.Do(req)

    if err != nil {
        log.Println(err)
        return
    }

    defer resp.Body.Close()

    decomBody := http.DecompressBody(resp)

    all, err := io.ReadAll(decomBody)
    if err != nil {
        log.Println(err)
        return
    }
    log.Println(string(all))

3.NodeJS部分修改

就是这盘文章中的内容:使用Node.js绕过TLS指纹识别.md

NodeJS是不能完全控制tls中的全部配置的,但是可以控制部分配置,所以当服务器只是简单使用黑名单来过滤一些客户端的话【它不可能使用白名单的,因为每个浏览器的tls配置都不同】,那我们更改一部分配置就可以绕过过滤了。

比如将密码排列方式改一下,如下:

const tls = require('tls');
const https = require('https');

const defaultCiphers = tls.DEFAULT_CIPHERS.split(':');
const shuffledCiphers = [
    defaultCiphers[0],
    // Swap the 2nd & 3rd ciphers:
    defaultCiphers[2],
    defaultCiphers[1],
    ...defaultCiphers.slice(3)
].join(':');

request = require('https').get('https://en.zalando.de/api/navigation', {
    ciphers: shuffledCiphers
}).on('response', (res) => {
    console.log(res.statusCode); // Prints 200
});

当然这种方法在要求仅仅使用某些浏览器时或全过程使用同一种浏览器时可能就不行了。

# 网络安全 # web安全 # 网络安全技术
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 thundersword 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
thundersword LV.4
这家伙太懒了,还未填写个人描述!
  • 9 文章数
  • 30 关注者
pwncollege通关笔记:4.Shellcode Injection(从0开始学习pwn)
2022-02-07
pwncollege通关笔记:3.Assembly Refresher(从0开始学习pwn)
2022-02-07
pwncollege通关笔记:2.Program Misuse(从0开始学习pwn)
2022-01-31
文章目录