共计 3067 个字符,预计需要花费 8 分钟才能阅读完成。
背景
最近碰到一个神奇的网站,在浏览器能够关上,然而通过 curl 或者 代码拜访就间接 403,我估摸着这必定是做了UA 校验
,于是申请的时候把浏览器的 UA 给带上,而后拜访发现还是 403,不过这也难不倒我,必定是还有校验其它的申请头,间接浏览器关上 network,把所有的申请头复制过去并且带上,确保我和浏览器在 http 协定层面的申请齐全一样,这样不可能会失败了吧,然而运行完发现还是 403。
放个地址: https://pixabay.com
思考
服务端校验客户端没有什么黑魔法,因为都是通过 TCP 协定通信,不可能存在浏览器发送一个 HTTP 报文和我发送一个同样的 HTTP 报文服务器能辨认进去,既然不是校验的 HTTP 层,那只可能是在 TLS 层校验的,于是祭出 wireshark
抓包,看看能不能找到 TLS 握手中差异化的货色,家喻户晓在 TLS 握手时有一个客户端发送给服务端的 Client Hello
报文,很有可能就是依据它来分别浏览器和非浏览器申请的,因为在这个报文中,客户端要通知服务端反对的加密套件,TLS 版本等等信息,而这些信息依据客户端的实现都会有所差别,先抓个失常浏览器申请的报文看看,如下图:
而后再通过 curl 拜访抓包,如下图;
能够看到两边的报文的确存在很大的差别,逐个比照排查之后发现很有可能是因为 curl 的申请报文里短少 supported_versions
扩大信息导致的 403,浏览器那边在此扩大信息内容如图:
示意反对 TLSv1.2
和TLSv1.3
,而且最终握手之后的协定也是切换到了 TLSv1.3
,在下面两个比照图能够看到,浏览器走的是TLSv1.3
,而 curl 走的是TLSv1.2
,可能是肯定要应用TLSv1.3
能力拜访胜利。
验证
马上 google 了下如何指定 curl 的 TLS 版本,发现只须要加上 --tlsv1.3
参数就能够了,如下:
$ curl -I --tlsv1.3 'https://pixabay.com/' \
> -H 'accept-language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6' \
> -H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.49'
HTTP/2 200
date: Fri, 22 Jul 2022 02:40:35 GMT
content-type: text/html; charset=utf-8
cf-ray: 72e8cffc18c73d5a-HKG
cache-control: s-maxage=86400
content-language: en
vary: Accept-Encoding, Cookie, Accept-Language
cf-cache-status: MISS
content-security-policy: frame-ancestors none
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
referrer-policy: strict-origin-when-cross-origin
x-frame-options: DENY
set-cookie: __cf_bm=Cy4a751rDND6kHhu.RzEr5DpqnaxRdpUxaMfNfkya0A-1658457635-0-AS1DaewDqNjWHZ/m74A88bNyEG0EFsZAwmsm/ON5QQEuh8B6XOS7PkSnhGgXPLV+LtEvzOKTy/WWHmwY63uGlD0=; path=/; expires=Fri, 22-Jul-22 03:10:35 GMT; domain=.pixabay.com; HttpOnly; Secure; SameSite=None
server: cloudflare
alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400
通过重复验证,发现除了要指定 tlsv1.3
之外,还须要加上 accept-language
和user-agent
头,并且肯定得是 http2 协定,三个条件缺一不可。
nodejs 拜访
下面说到了肯定要走 http2 协定,而当初市面上风行的 http client 根本都是只反对 http1.1,所以只能间接从根底库动手了,官网有一个 http2
的库,一番调教之后也是胜利申请了,代码如下:
const http2 = require("http2");
function get(host, path) {return new Promise((resolve, reject) => {const session = http2.connect(`https://${host}`, {
minVersion: "TLSv1.3",
maxVersion: "TLSv1.3",
});
session.on("error", (err) => {reject(err);
});
const req = session.request({[http2.constants.HTTP2_HEADER_AUTHORITY]: host,
[http2.constants.HTTP2_HEADER_METHOD]: http2.constants.HTTP2_METHOD_GET,
[http2.constants.HTTP2_HEADER_PATH]: path,
"user-agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36 Edg/100.0.1185.50",
});
req.setEncoding("utf8");
let data = "";
req.on("data", (chunk) => {data += chunk;});
req.on("end", () => {session.close();
if (data) {
try {resolve(data);
} catch (e) {reject(e);
}
}
});
req.on("error", (err) => {reject(err);
});
req.end();});
}
(async function () {const data = await get("pixabay.com", "/");
console.log(data);
})();
深刻
尽管曾经胜利申请了,然而本着摸索的精力持续深刻发现 cloudflare 官网有一篇博客就是专门介绍这个 TLS 拦挡技术的,链接如下:
https://blog.cloudflare.com/monsters-in-the-middleboxes/
其中有一段内容也证实了我的猜测,翻译后如下:
也就是说 cloudflare 会保护一组浏览器的 TLS 指纹,当收到一个 Client Hello 申请时,会查看这组指纹,如果匹配不上,就会拦挡这个申请,这样能够拦挡掉大部分不是来自浏览器的申请了。
我是 MonkeyWie,欢送扫码👇👇关注!不定期在公众号中分享
JAVA
、Golang
、前端
、docker
、k8s
等干货常识。