不過這次最特別的是我在 HackMD 團隊修復後還是有找到方法 bypass 它的 filter,在一樣的地方再拿一個 XSS。後來回顧時還發現到了一個先前沒找到的 XSS,所以這篇文章一共有三個 HackMD XSS。
1 | a.fn.gist = function(c) { |
顯然,裡面有各種字串拼接 html 的操作,所以應該可以有 XSS。然而 data-gist-id
在前面會被 stripTags
過濾,所以要看看它是怎麼做的:
這邊 t
部分因為呼叫時沒有給參數,所以會是預設的那個 [""]
,因此它相當於 e.replace(/<\/?[^<>]*>/gi, "")
,所以 <<x>script>
就能繞過了,之後就想辦法讓它觸發 error callback 的 d.html("Failed loading gist " + f + ": " + b)
就行了。
基本上是 replace 一樣的東西,但是這次還會 recursive 的把 html tag 移除掉,所以 <<x>script>
這種繞法是沒用的。不過由於它有 match >
,所以 <script
這種沒閉合的 tag 是不會被移除的。
在不能用 >
的情況下從 cheat sheet 上查可知有 <svg onload=alert()//
之類的 payload,不過這邊一個是因為 CSP 所以不能用,另一個是它 html 注入的地方是使用 jQuery $.fn.html
去做的,裡面會 assign html 給 innerHTML
。而瀏覽器在 assign html 給 innerHTML
時我發現如果只有 <
存在而沒有 >
的話,<
之後的所有 payload 都會整個消失不見,所以這樣是沒辦法的。
前面可以很簡單的把 img 結束掉,後面又有 >
的存在,所以只有 <
的話可以用 iframe srcdoc 去搞事。不過這邊有個困難點是 srcdoc 裡面還是要 bypass CSP,因此它一樣要用 Google jsonp CSP bypass,但同時它又有個呼叫 Gist 的 ajax,無論是 success 還是 error 都會呼叫 d.html(...)
,iframe 還在載入中的 Google jsonp 覆蓋過去,所以沒辦法 XSS。
這邊我的想法是能不能讓它憑空生一個 error,讓它不會執行到 d.html(...)
,這樣才有足夠的時間能載入 jsonp。而這邊我的答案就是在 Gist callback 利用我之前在 HITCON CTF 2022 - Secure Paste 用的那個 jsonp delete 技巧 (實際上真正的來源應該是這個),用 delete[jQuery.fn][0].html
就能把 $.fn.html
刪掉,這樣就能讓它產生 error 了,所以就有足夠的時間讓 Google jsonp 載入得到 XSS。
最後有個我原本沒注意到的點,就是 setTimeout(window.viewAjaxCallback, 200)
這個地方。原本 window.viewAjaxCallback
是有個函數存在的,但我們從上面知道可以用 jsonp callback 去 delete 某個東西,所以用 delete[window][0].viewAjaxCallback
就能把它刪掉。
接下來因為 setTimeout
的第一個參數其實也能接受 string,所以有機會用 DOM clobbering 去蓋掉 window.viewAjaxCallback
的值達成 XSS: