1 背景
研究人员开发者使用了一种检测方法,并提供了检测页面对chrome浏览器进行检测,
判断是否是无头浏览器。具体返回结果如下图。
2 检测手段分析
常见的前端检测手段即通过前端JS脚本收集数据上报,然后检测浏览器特征判断是否是无头浏览器。分析过程即看请求中使用的分析脚本及上报数据。首先打开正常浏览器可以看到如下7个请求。
https://arh.antoinevastel.com/bots/scannerareuhead 发出的post请求会上报设备信息,如下为截取部分结果。
{
"plugins":[
"Chrome PDF Plugin::Portable Document Format::internal-pdf-viewer::__application/x-google-chrome-pdf~pdf~Portable Document Format",
"Chrome PDF Viewer::::mhjfbmdgcfjbbpaeojofohoefgiehjai::__application/pdf~pdf~",
"Native Client::::internal-nacl-plugin::__application/x-nacl~~Native Client Executable,application/x-pnacl~~Portable Native Client Executable"
],
"mimeTypes":[
"~~application/pdf~~pdf",
"Portable Document Format~~application/x-google-chrome-pdf~~pdf",
"Native Client Executable~~application/x-nacl~~",
"Portable Native Client Executable~~application/x-pnacl~~"
],
"userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3902.4 Safari/537.36",
"byteLength":1,
"gamut":[
true,
true,
false,
true
],
"anyPointer":"fine",
"anyHover":"hover",
"appVersion":"5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3902.4 Safari/537.36",
"appName":"Netscape",
"appCodeName":"Mozilla",
"onLine":true,
"cookieEnabled":true,
"doNotTrack":false
}
为了绕过检测,就是让各种数据和真实设备一致。即发出的请求尽量完全一致(请求头、请求体)。首先验证设备参数的影响,从真实浏览器中提取设备数据存放在mockFinger文件中,当发送请求时将body替换观察结果。
const fingerprint = JSON.parse(
fs.readFileSync("./mockFinger").toString()
);
const headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3902.4 Safari/537.36'
};
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', async interceptedRequest => {
const overrides = {
headers: {
...interceptedRequest.headers(),
...headers
}
};
if (interceptedRequest.url() === 'https://arh.antoinevastel.com/bots/scannerareuhead') {
fingerprint.uuid = await page.evaluate(() => uuid);
fingerprint.url = page.url();
const postData = JSON.stringify(fingerprint);
overrides.headers['content-length'] = postData.length;
overrides.postData = postData;
}
interceptedRequest.continue(overrides);
});
await page.goto(url);
发送请求后发现结果没变化,页面返回结果提示当前仍然是无头浏览器。 由于请求除了body外,还有header等数据。因此尝试修改header中的参数来观察结果。
修改依然通过将真实设备数据复制过来观察结果。直接修改header,在header中增加Accept-Language。再次运行页面检测提示当前浏览器不是无头浏览器。尝试将Accept-Language改为小写accept-language,发现服务端是对chrome浏览器的Accept-Language进行了存在性及大小写验证。调整后即可绕过检测。
const puppeteer = require('puppeteer');
(async () => {
const puppeteer = require('puppeteer');
const browserOpts = {
headless: true,
args: ['--no-sandbox',
'--disable-setuid-sandbox',
'--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3902.4 Safari/537.36\',// \'Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0',
'--remote-debugging-port=9222'],
executablePath: "/XXX/XXX/XXX/XXX/chrome-mac/Chromium.app/Contents/MacOS/Chromium"
};
const browser = await puppeteer.launch(browserOpts);
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request',(interceptRequest) => {
const headers = Object.assign({},
interceptRequest.headers(),{
'Accept-Language': 'zh-CN,zh;q=0.5'
});
interceptRequest.continue({headers});
});
await page.goto('https://arh.antoinevastel.com/bots/areyouheadless');
await browser.close();
})();
除了通过拦截的方式修改header,也可以在args参数中指定命令行参数设置语言选项 ‘–lang=zh-CN,zh;q=0.5’。而通过setExtraHTTPHeaders方法会将大写变成小写,导致无法通过检测。
3 总结
检测分析通常使用脚本收集浏览器特征来分析。但访问一个网站还会发出很多请求,因此除了脚本上报特征也需要关注请求数据。模拟设备与真实设备依然存在微小的差异点。因此在反爬工作与爬虫工作中应从访问的流程上全面考虑可获取到的信息,并通过分析系统分析。
4 参考
[1]对象的简洁表示,http://es6.ruanyifeng.com/#docs/object
[2]chrome命令行参数,https://peter.sh/experiments/chromium-command-line-switches/
[3]are you headless分析,https://news.ycombinator.com/item?id=20479015
[4]request.continue方法,https://github.com/GoogleChrome/puppeteer/blob/v1.19.0/docs/api.md#requestcontinueoverrides