Exploit Headless Chrome

背景

Chrome M59引入了 Headless Chrome,至此可以在无GUI的环境下使用Chrome,极大的方便了自动化测试工作,也可以用于预览服务,或者网络爬虫。

前端并不是我的强项,我在上网冲浪的时候发现一个很有趣的现象:

  1. 很多技术文章相互参考,有些代码自然也直接复制粘贴使用
  2. Headless Chrome自然也是这样,一些不好的编程习惯也被错误地传播

于是有了本文,以及一些个人的思考。

思考 🤔

首先,在Google搜索Headless Chrome相关的技术文章,会跳出来官方文档以及各种技术文章:

(关键词顺序反了,不过不影响 XD )

CleanShot 2021-05-26 at 15.41.22@2x

Headless Chrome其实就是从命令行启动Chrome,传递了--headless的参数,那么对于Chrome来说,有很多参数,但是有那么几个参数非常危险,比如 : --no-sandbox--disable-web-security,以及开启调试端口…

在看了很多网上的文章后,我发现有不少代码是重复的(关键逻辑),比如这篇:

CleanShot 2021-05-26 at 15.47.37@2x

对于错误的参数也是一样的,比如不约而同地关闭sandbox:

1
2
3
const browser = await puppeteer.launch(
{args: ['--no-sandbox', '--disable-setuid-sandbox'] }
);

或者调试端口不是 9222就是9229这类情况。 这里其实有几个问题:

  1. 我使用的参数是什么意思?
  2. 这些参数对我的程序有什么影响?
  3. 这些参数安全吗?
  4. 什么情况下我可以用,什么情况下不能使用?

如果搞不清楚,对于只是个人学习来说搞个demo那倒还好,如果说是用于实际项目,比如写预览服务,爬虫等项目,还是直接使用了这些危险的参数那就太危险了。

  1. 线上环境要求稳定,不一定是最新版本node,即不一定是最新版本Chrome
  2. 危险的参数(比如--disable-web-security),没有开启沙箱,开了调试端口等

老版本Chrome + NOSANDBOX = RCE 🤔

至此,我认为可以搞个Demo验证一下这个攻击思路是否可行。

Demo

Pages

这里直接扒了xlab某次安全推送,然后在本地跑起来,假装是一个目标网页:

CleanShot 2021-05-26 at 15.56.06@2x

下面搞两个场景吧,第一个是预览,简化一下,截图好了;第二个是爬虫,爬取这网页上的信息。

Demo1 : 预览

预览的场景有很多,比如常见的IM中,发送的URL可能会被渲染成“卡片”,不同IM处理不一样,一般来说只有白名单才会这样。

我这边不会搞太复杂的东西,就直接用截图代替了,直接也从网上”东拼西凑“点代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const puppeteer = require('puppeteer');


(async () => {
targetUrl = "https://www.google.com.hk";

try {
const args = process.argv.slice(2)
targetUrl = args[0];
} catch (e) {
console.log(e);
}

// FIXME : I should open SANDBOX, this cmdline is wrong !!!
const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome',
args: ['--no-sandbox', '--disable-setuid-sandbox'] });
const page = await browser.newPage();

await page.goto(targetUrl);
await page.screenshot({ path: 'res.png' });

await browser.close();
})();

功能也比较简单,预览指定的网页,我这里搞得简单,直接截图然后保存,IM里那种卡片式不知道怎么搞,就没去尝试,不过也是个攻击面啦 🤣

跑一下Demo:

CleanShot 2021-05-26 at 15.57.20@2x

如果在页面里加载恶意的JS呢?

  • 直接使用script 标签加载

  • 加入js,使用js web worker加载

BOOM :

CleanShot 2021-05-26 at 15.59.02@2x

Demo2 : 爬虫

这里直接抄了https://www.anquanke.com/post/id/103350 里的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ cat crawler.js 

const puppeteer = require('puppeteer');

(async () => {
// FIXME : I should open SANDBOX, this cmdline is wrong !!!
const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', args: ['--no-sandbox', '--disable-setuid-sandbox'] });
const page = await browser.newPage();

await page.goto('http://localhost/foo.html', {
waitUntil: 'networkidle0'
});

//count
let eleCount = await page.evaluate((sel) => {
return document.getElementsByClassName(sel).length;
}, 'category');


if(eleCount != 0){
let htmlArray = await page.evaluate((sel, eleCount) => {
let element = document.querySelectorAll(sel);
let htmlArray = [];
for(let i = 0; i <= eleCount; i++){
htmlArray[i] = element[i].innerText;
}
htmlArray.shift();
return htmlArray;
}, 'p', eleCount);
console.log(htmlArray);
}


await browser.close();
})();

预期行为:

CleanShot 2021-05-26 at 16.00.39@2x

看起来不错 : )

加入恶意的JS之后:

CleanShot 2021-05-26 at 16.01.20@2x

这里有个地方不完美,我本来尝试js web worker加载exp,想 爬虫正常工作,爬取到需要的内容,exp在后台跑,但是我发现要么时间不够我跑exp(需要爬虫停留的久一点),要么就exp跑了,但是内容没爬取到。这点我认为应该可以解决,如果有知道的前端大佬可以分享一下~

后记

那么是否存在这样一条攻击链,针对爬虫或者一些Headless Chrome的服务:

  • 恶意构造页面,集成多个Exploit,覆盖大量Chrome版本,打进去就挖矿or种勒索?

img

最后还是建议以官方文档为准,写代码参考文档而不是从网上的技术文章里摘 : )

Demo & Exp

https://github.com/o0xmuhe/headless_chrome_demo

参考