如何进行浏览器完整性检查
目录
我的上一篇博客,提到了浏览器完整性检查,本文对此进行探讨。
Context
在本文中,浏览器完整性检查是指识别非来自人操作浏览器来的HTTP流量。
相关理论
人操作浏览器,典型场景是发送一个GET或POST请求,然后得到一个html响应内容。
这在期间有许多的特征可以用来检测请求流量是否来自人,而非机器发出。
例如
- 特定HTTP头(Accect等)
- 特定能力(能执行指定js脚本)
等
一些检测方法
示例
这是一个示例,进行简单的浏览器完整性检查
ua := ctx.GetHeader("User-Agent")
u := useragent.Parse(ua) // "github.com/mileusna/useragent"
if u.Name == "" || u.OS == "" {
ctx.String(http.StatusForbidden, "User-Agent验证未通过")
ctx.Abort()
return
}
//TODO:进行更多验证
if (!strings.Contains(ctx.Request.Header.Get("Accept"), "text/html") && !strings.Contains(ctx.Request.Header.Get("Accept"), "*/*")) ||
!strings.Contains(ctx.Request.Header.Get("Accept-Encoding"), "gzip") ||
ctx.Request.Header.Get("Accept-Language") == "" ||
ctx.Request.Header.Get("Sec-Fetch-Site") == "" {
ctx.String(http.StatusForbidden, "浏览器完整性验证未通过")
ctx.Abort()
return
}
HTTP头
通过以下这些HTTP头可以用来进行浏览器完整性检查
- User-Agent:这是最常用的,用来提升请求来源,浏览器通常设置为类似
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36 Edg/147.0.0.0的字符串,而各个编程语言则默认设置为类似Go-http-client/1.1这种标明了自己身份的字符串,尽管可以修改伪装,但这仍然是很有用的一种检测方法。 - Accept:通过浏览器包含text/html,表面它可以接收网页,OPTIONS请求时则包含*/*,对于伪装了User-Agent的流量,可能不会伪装其它标头
- Accept-Encoding,Sec-Fetch-Site,Accept-Language,Sec-Ch-Ua,Sec-Ch-Ua-Mobile,Sec-Ch-Ua-Platform,Sec-Fetch-Dest,Sec-Fetch-Mode是一些相对在伪装非来自人的机器流量时,更冷门的HTTP头,但在真实浏览器中通常是存在且有相应含义内容的。
- Referer: 浏览器里跳转而来的请求,通常有这个HTTP头,这可以用来排除一些不是来自真人操作浏览器的网络API调用。
- 自定义头:可以通过自定义一些HTTP头,更好的帮助服务器检查流量。比如设置GET请求后js设置一个CSRF-TOKEN,POST请求时检查Cookie中是否包含此。
执行js能力
如果是真实的用户操作浏览器,一般不会禁用js(特殊情况不讨论),而使用各种编程语言实现的爬虫,默认不具备执行js的能力,往往需要外部库,或调用无头浏览器。 利用这一点,我们可以进行浏览器完整性检查。
一些思路如下
- 响应请求前返回js脚本执行一些hash运算,比如服务器给字符串x,请求者算SHA512(x)并返回结果给服务器,检查是否能正确执行js脚本
- 检查js的navigator.webdriver是否true
检查TLS指纹
TLS指纹也是一种浏览器完整性检查方法,不过需要收集来自非真实浏览器的TLS指纹做黑名单,或来自真实真实浏览器的TLS指纹做黑名单,两个都不简单,故此不是很推荐常规使用。