绕过TLS指纹 WAF检测 Akamai指纹HTTP反向代理(GO语言)
前言
逆向模拟的时候都会遇见这种情况:浏览器发送请求直接通过,转成py发送请求就会被拦截,无论header,data,cookie怎么对照浏览器写,或者是用curl_request和别的发包工具都会被精准拦截。问题就出现在指纹+waf检测
测试指纹拦截网址:aHR0cHM6Ly93d3cuY2VhaXIuY29tL3poLw==
TLS指纹
1.什么是TLS指纹:
TLS指纹是一种用于识别和验证TLS(传输层安全)通信的技术。
TLS指纹可以通过检查TLS握手过程中使用的密码套件、协议版本和加密算法等信息来确定TLS通信的特征。由于每个TLS实现使用的密码套件、协议版本和加密算法不同,因此可以通过比较TLS指纹来判断通信是否来自预期的源或目标。
TLS指纹可以用于检测网络欺骗、中间人攻击、间谍活动等安全威胁,也可以用于识别和管理设备和应用程序。
2.常见TLS指纹对比:
测试网站:https://tls.browserleaks.com/json
requests:
curl_cffi:
Edg:
Chrome:
不同客户端的指纹不同。拿curl_cffi来解释一下含义
第一个值771:表示 JA3 版本,即用于生成指纹的 JA3 脚本的版本。
第二个值:4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53客户端按顺序请求的密码套件
第三个值:16-43-11-65281-45-27-23-51-10-0-18-35-5-17513-13-21 TLS扩展字段
第四个值:29-23-24 Elliptic Curver椭圆曲线
第五个值:0 EC Point Formats 这个0代表非压缩格式
3.常见的TLS指纹绕过方法
修改客户端Hello报文中的字段(如TLS版本、加密套件、扩展列表),使其与合法客户端一致。使用工具或库(如curl、Python的urllib3)定制TLS握手行为。
利用中间层代理(如mitmproxy、Gost)重写TLS指纹,或通过云函数(如AWS Lambda)转发流量以隐藏原始指纹。
模拟浏览器指纹(如Chrome/Firefox的TLS特征),通过开源库(如tls-client)生成合法指纹。
WAF检测(Akamai的子类)
1.什么是WAF检测:
WAF(Web Application Firewall,Web 应用防火墙)是部署在网站入口的一道安全屏障,它会检查每一个 HTTP 请求是否符合正常浏览器的行为特征:从网络层的 TLS 指纹(JA3/JA4,记录客户端的密码套件、扩展、椭圆曲线等参数)、HTTP/2 帧参数、TCP 连接特征,到应用层的请求头、Cookie、请求频率和访问路径,综合判断请求是来自真实用户还是爬虫/攻击工具,可疑请求会被拦截或触发验证码,正常请求则放行到后端服务器。
2.Akamai指纹
拿谷歌浏览器讲
指纹是:1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p
SETTINGS 帧:1:65536;2:0;4:6291456;6:262144
1. 1:65536:HEADER_TABLE_SIZE即头部表大小为64KB,指的是用于存储请求头和响应头的大小,它是可以调整的。这个字段指明了使用64KB的头部表大小
2. 2.0:HTTP2_VERSION,指示此请求使用的HTTP/2版本。0表示H2,表示启用了HTTP/2协议。
3. 6291456:INITIAL_WINDOW_SIZE流级别初始窗口,约 6MB
4.262144:MAX_HEADER_LIST_SIZE请求头最大字节数,256KB
WINDOW_UPDATE 帧:15663105 计算方式是INITIAL_WINDOW_SIZE+ 10485760 - 1
PRIORITY 帧的流 ID:0 表示客户端对整个链接设置优先级
伪头顺序:m,a,s,p
m:method 请求方法
a:authority 主机名如www.xxxx.com
s:scheme 协议,如https
p:path路径 如/xxx/xxx
可以根据这个顺序来区分浏览器类型
HTTP反向代理
了解了tls指纹和akamami指纹和waf检测,就可以去规避了,基本上的tls指纹用curl_cffi就可以通过,但是waf检测不仅仅检测了tls指纹,它还检测了ja4,http/2指纹(H2SETTINGS帧),TCP层,请求频率,cookie,还有header。这里采用了go语言的原生tls指纹来请求,go的tls指纹模仿的会比市面上常见的requests,curl_cffi, httpclient等要更全面,更像浏览器
封装GO的反向代理
1.导包,声明基础信息
import (
"bufio"
"crypto/tls"
"fmt"
"io"
"log"
"net"
"net/http"
"net/http/cookiejar"
"net/url"
"strings"
"time"
)
const (
targetHost = "www.ceair.com"
proxyAddr = ":18080"
userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
)
2.初始化,创建COOKIE, Transport,创建http.Client
var client *http.Client
func init() {
jar, _ := cookiejar.New(nil)
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS12,
},
MaxIdleConns: 10,
IdleConnTimeout: 90 * time.Second,
ForceAttemptHTTP2: true,
DisableCompression: false,
}
client = &http.Client{
Transport: tr,
Jar: jar,
Timeout: 30 * time.Second,
}
}
3.warmup()模拟正常用户,携带响应cookie
func warmup() error {
req, _ := http.NewRequest("GET", "https://"+targetHost+"/", nil)
req.Header.Set("User-Agent", userAgent)
req.Header.Set("Accept", "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("homepage: %v", err)
}
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
log.Printf("[warmup] GET / -> %d", resp.StatusCode)
searchURL := fmt.Sprintf("https://%s/zh/cny/shopping/oneway/PEK-SHA/%s",
targetHost, time.Now().AddDate(0, 0, 7).Format("2006-01-02"))
req2, _ := http.NewRequest("GET", searchURL, nil)
req2.Header.Set("User-Agent", userAgent)
req2.Header.Set("Accept", "text/html,application/xhtml+xml;q=0.9,*/*;q=0.8")
req2.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
req2.Header.Set("Referer", "https://"+targetHost+"/")
resp2, err := client.Do(req2)
if err != nil {
return fmt.Errorf("search: %v", err)
}
io.Copy(io.Discard, resp2.Body)
resp2.Body.Close()
log.Printf("[warmup] GET search -> %d", resp2.StatusCode)
cookies := client.Jar.Cookies(req.URL)
names := make([]string, len(cookies))
for i, c := range cookies {
names[i] = c.Name
}
log.Printf("[warmup] cookies: %d %v", len(cookies), names)
return nil
}
4.handle()读取请求,构造参数
func handle(conn net.Conn) {
defer conn.Close()
conn.SetDeadline(time.Now().Add(120 * time.Second))
reader := bufio.NewReader(conn)
req, err := http.ReadRequest(reader)
if err != nil {
return
}
t0 := time.Now()
log.Printf("[req] %s %s", req.Method, req.URL.Path)
reqBody, _ := io.ReadAll(req.Body)
req.Body.Close()
targetURL := &url.URL{
Scheme: "https",
Host: targetHost,
Path: req.URL.Path,
RawQuery: req.URL.RawQuery,
}
resp, bodyBytes, err := doRequest(targetURL, req, reqBody)
if err != nil {
log.Printf("[req] error: %v", err)
writeError(conn, 502, "Bad Gateway")
return
}
if isCDNBlocked(bodyBytes) {
log.Printf("[req] CDN BLOCKED, rewarmup + retry...")
warmup()
resp2, body2, err2 := doRequest(targetURL, req, reqBody)
if err2 == nil {
resp = resp2
bodyBytes = body2
}
}
elapsed := time.Since(t0)
if isCDNBlocked(bodyBytes) {
log.Printf("[res] CDN_BLOCKED (%dms, %d bytes)", elapsed.Milliseconds(), len(bodyBytes))
} else {
log.Printf("[res] %d OK (%dms, %d bytes)", resp.StatusCode, elapsed.Milliseconds(), len(bodyBytes))
}
writeResponse(conn, resp, bodyBytes)
}
5.doRequest()发包转发到目标网站,最多两次重试
func doRequest(targetURL *url.URL, orig *http.Request, body []byte) (*http.Response, []byte, error) {
for i := 0; i < 2; i++ {
var bodyReader io.Reader
if len(body) > 0 {
bodyReader = strings.NewReader(string(body))
}
proxyReq, err := http.NewRequest(orig.Method, targetURL.String(), bodyReader)
if err != nil {
continue
}
buildHeaders(proxyReq, orig)
resp, err := client.Do(proxyReq)
if err != nil {
log.Printf("[req] attempt %d failed: %v", i+1, err)
continue
}
bodyBytes, _ := io.ReadAll(resp.Body)
resp.Body.Close()
return resp, bodyBytes, nil
}
return nil, nil, fmt.Errorf("all retries failed")
}
6.返回响应,并重新计算content-length等值
func writeResponse(conn net.Conn, resp *http.Response, bodyBytes []byte) {
fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\n", resp.StatusCode, http.StatusText(resp.StatusCode))
for k, vs := range resp.Header {
if k == "Transfer-Encoding" || k == "Content-Length" || k == "Content-Encoding" {
continue
}
for _, v := range vs {
fmt.Fprintf(conn, "%s: %s\r\n", k, v)
}
}
fmt.Fprintf(conn, "Content-Length: %d\r\n", len(bodyBytes))
fmt.Fprintf(conn, "Connection: close\r\n\r\n")
conn.Write(bodyBytes)
}
融合到代码
将url替换成代理接口,然后转发给代理,代理去请求目标网站。
TLS_PROXY = "http://localhost:18080"
brief_url = f"{TLS_PROXY}/portal/v3/shopping/briefInfo"
结果对比
正常使用curl_cffi请求
使用代理请求
法律与道德边界
强调技术研究的合规性,避免用于非法渗透测试或绕过安全防护。建议在授权测试环境中验证技术可行性。
个人观点,仅供参考学习。如有侵权,联系下架
更多推荐
所有评论(0)