Optional. The name of your callback function, executed when reCAPTCHA encounters an error (usually network connectivity) and cannot continue until connectivity is restored. If you specify a function here, you are responsible for informing the user that they should retry.
當 reCAPTCHA 載入失敗的時候,就會呼叫這個屬性寫的 function,所以下一步就是看程式碼中有哪些 function 可以利用,找到了這個:
function logout() {
document.getElementById('logout-form').submit()
}
原本的 logout-form 的位置在筆記插入的位置下面,所以我們可以用別的 form 把它蓋掉。
因為 DOMPurify 預設不會過濾 form,也不會過濾 data- 開頭的屬性,所以可以用下面這段 HTML 來讓網頁重新導向:
上個週末除了有我上一篇心得寫的 SUSCTF 2022 以外,還有另外一個 TSJ CTF,裡面也有很多好題,因為時間不太夠所以我只有挑了自己比較有興趣的題目來看,就是標題說的這題 Nim Notes,最後沒解開(還差得遠呢),但解法十分有趣,因此寫一篇來記錄一下官方解法。
作者(maple3142)的 writeup 在這:https://github.com/maple3142/My-CTF-Challenges/tree/master/TSJ%20CTF%202022/Nim%20Notes
題目介紹與解法
題目敘述:
簡單來說又是一個 CTF 常見的 note taking web app,登入之後可以新增筆記,也可以回報自己的筆記頁面給 admin bot。這題在 render 筆記頁面時,不是從後端直接吐資料,而是從前端打 API 去拿,再 render 在畫面上。
筆記頁面的 HTML 長這樣:
重點在於 app.js,主要邏輯都在裡面:
唯一可以注入的點在這邊:
el.querySelector('.note-content').innerHTML = DOMPurify.sanitize(marked.parse(note.content))
,但因為有經過DOMPurify.sanitize
,所以沒辦法直接 XSS,而這題的 CSP 也滿嚴格的:script 基本上只能引入自己或是 reCAPATCHA 的,沒有
unsafe-inline
可以用,其他東西像是 style 也被封死,所以也不會是 CSS injection。我嘗試找了一下 DOM clobbering 能不能幹嘛,但看了一下沒找到可以利用的地方。原本看到這個 CSP,我以為
https://www.google.com/recaptcha/
的地方可以用%2f
來繞過,例如說 JSONBee 上面有個 google.com 的 JSONP payload:如果把網址改成:
https://www.google.com/recaptcha/..%2fcomplete/search?client=...
,瀏覽器會過 CSP,而有些伺服器會把..%2f
解讀成../
,所以就會是我們上面的 endpoint。不過實際嘗試過後發現這招對 google 行不通,只會回傳一個 404 not found,所以無法利用。總之呢,大概卡了一兩個小時,連第一步怎麼開始都找不到。
後來官方有給了兩個提示,第一個提示是說第一步要先讓 admin bot 可以訪問到自己的網站,因此筆記頁面應該有個可以重新導向的漏洞。第二個則是明顯提示第一步的關鍵在 reCAPATCHA,仔細看文件就會找到答案。
從文件中不難發現有個屬性可以利用:
data-error-callback
,描述是:當 reCAPTCHA 載入失敗的時候,就會呼叫這個屬性寫的 function,所以下一步就是看程式碼中有哪些 function 可以利用,找到了這個:
原本的 logout-form 的位置在筆記插入的位置下面,所以我們可以用別的 form 把它蓋掉。
因為 DOMPurify 預設不會過濾 form,也不會過濾
data-
開頭的屬性,所以可以用下面這段 HTML 來讓網頁重新導向:第一階段就這樣完成了,可以把 admin bot 導到任意頁面,當初看完提示解到這邊我就卡住了,看很久看不出下一步可以做什麼。
看了一下解答,第二階段是
setCookie
的 CRLF injection,但因為注入的點在 CSP header 下面,所以沒辦法 disable CSP,因此就算你可以控制 response,理論上也沒辦法 XSS,這時候就來到了第三階段。在講第三階段之前先提一下非預期解,破除了「理論上沒辦法 XSS」這個前提,原因出在 nim 的 sqlite library 在內容含有
\0
的時候會爆炸,爆炸之後會直接噴錯誤訊息而且沒有 CSP header,就得到了一個開心的 XSS。繼續講回精彩的第三階段。
第三階段是利用 Content-Security-Policy-Report-Only 這個 header 來 leak flag,主要是因為違反規則時,會發送一段 JSON 到 server,其中有個
script-sample
,如果是 inline script 違反的話會有前 40 個字元(還有一個前提,那就是 CSP 要加上report-sample
)。我們可以自己弄個簡單的 server 來驗證:
從瀏覽器就可以看到送出去的 request 長怎樣:
靠著這個,就可以順利拿到 flag,打完收工。
其他可能性
在寫這篇的時候,我邊寫邊在每一個環節想有沒有其他可能性,但是在原題這麼嚴格的條件底下,能想到的路很有限,不過如果在原題上面做一些變化,放寬一些限制,倒是有其他可能。
Open redirect
這個是原題不變的狀況下我唯一能想到的其他解法,就是找到
https://www.google.com/recaptcha/
或是https://www.gstatic.com/recaptcha/
的 open redirect 或 JSONP,就可以繞過 CSP 去執行 JavaScript。CSS injection
假如 CSP 放寬,script 的部分一樣擋住,但是其他 style 相關的像是 style-src、font-src 跟 img-src 之類的都不擋的話,那在第三階段似乎有機會用 CSS injection 的方式把 flag 慢慢 leak 出來。