function isCustomComponent(tagName: string, props: Object) {
if (tagName.indexOf('-') === -1) {
return typeof props.is === 'string';
}
switch (tagName) {
// These are reserved SVG and MathML elements.
// We don't mind this list too much because we expect it to never grow.
// The alternative is to track the namespace in a few places which is convoluted.
// https://w3c.github.io/webcomponents/spec/custom/#custom-elements-core-concepts
case 'annotation-xml':
case 'color-profile':
case 'font-face':
case 'font-face-src':
case 'font-face-uri':
case 'font-face-format':
case 'font-face-name':
case 'missing-glyph':
return false;
default:
return true;
}
}
這次有兩題 Web 比較難,解掉了一題,另一題解不開但解法超值得一看,照樣簡單寫個心得。
Noted
連結在這:https://play.picoctf.org/practice/challenge/282
簡單來說狀況是這樣的,這是一個常見的可以新增 note 的系統,在 /notes 頁面可以看到自己所有的 note,然後有個 self XSS,然後現在有個處於登入狀態下的 admin 會造訪你提供的 URL,要想辦法拿到 admin 的筆記內容。
程式碼:
bot 的程式碼:
題目敘述有說這題的 admin bot 沒有對外連線的功能,所以增加了一些額外的難度(不過其實有開就是了,看 discord 好像開賽前加回去了)。
不過這點先不討論,先來討論其他的。
這題有 csrf token 來防止 csrf,但登入的部分沒有,所以可以用 csrf 來登入,就可以順利在 admin 上執行 XSS。
如果 same site cookie 沒設定的話,其實就 iframe 一下很容易,像這樣:
先在頁面上面開好 iframe,裡面就會有 admin 的筆記,然後用 form csrf 一下,新開一個視窗出來。
接著只要在自己的帳號裡面弄出 XSS,用
window.opener.frames['f'].document.body
就可以拿到頁面的內容了, 雖然新開啟的頁面跟 window.opener 不同源,但因為跟window.opener.frames['f']
同源所以沒差,一樣可以存取到。不過這題的最大問題是 Chrome 預設的 default Lax,所以 iframe 不會帶 cookie,就沒辦法用了。
一個很直觀的解法是改用
window.open
來做,像這樣:但最大的問題是,在新開的視窗中沒辦法用
window.opener.win
來存取到開啟的 window,因為跟window.opener
不同源,所以不讓你存取上面的東西。如果兩個新開的 window 彼此間不能溝通,這該怎麼辦呢?
想了一陣子之後,突然靈機一動:「那就直接把
window.opener
變成要拿的頁面不就好了?」像這樣:
有種 race condition 的感覺,表單送出之後立刻跳轉頁面,這時候因為新的登入還沒完成,所以跳過去時還是 admin 的 session,因此在新的視窗登入完成後就可以直接拿
window.opener.document
的東西。如果有連網的話,就樣就完成了。在沒有對外連線的情況下,可以發現 admin bot 沒有檢查網址,所以可以傳
javascript:
或是data:
之類的東西給它,回傳 flag 的部分可以直接用新增筆記的方式。讓 admin bot 訪問底下這段,其實就只是用
data:text/html
載入 html 而已:XSS payload,這邊直接新開一個 window 去做就好,也可以用 iframe 來做
另一種解法
運用我前幾天在 iframe 與 window.open 黑魔法學到的招數,也就是「當開啟同名 window 時會拿到 reference,不會新開 window」,這樣兩個新開的 window 就可以互相溝通了。
XSS 的部份這樣寫:
這篇也用了類似的解法:https://github.com/Scoder12/ctf/blob/main/PicoCTF%202022/web_noted.md
Live Art
連結:https://play.picoctf.org/practice/challenge/277?page=1&search=live
這題我還沒有自己重現過,解法都是從這篇 picoCTF 2022 WriteUps 看來的,簡單記一下,主要的漏洞有兩個。
is
屬性攻擊 React第二點特別有趣,所以這邊特別講一下第二點。
請問底下的 React app 有什麼問題?
這邊就只是把網址列上的 query string 變成 object 後丟進 img 去,
URLSearchParams
預設的用法沒支援 array 跟 object,所以想產生dangerouslySetInnerHTML: { __html: '..'}
是不可能的。換句話說,今天如果可以控制 React render 中元素的 props,但是 value 只能是字串,可以做些什麼?
當 React 在設置屬性時,如果你寫
<img onError="alert(1)">
,會跳出錯誤訊息:改成小寫的版本,則是會跳 warning:
相關的檢查都在這邊:https://github.com/facebook/react/blob/v18.0.0/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js#L275
而這邊可以注意到有個
isCustomComponent
的判斷,程式碼在這:https://github.com/facebook/react/blob/v18.0.0/packages/react-dom/src/shared/isCustomComponent.js只要 props 內的 is 是字串的話,就會是 true。
而 React 在設置屬性時也會有一些檢查:https://github.com/facebook/react/blob/v18.0.0/packages/react-dom/src/client/DOMPropertyOperations.js#L151
簡單來說,如果是 custom element(props.is 是個字串)的話,很多屬性都會直接設定上去。
所以,如果有底下的程式碼:
就可以觸發 XSS!因為 is 的緣故,讓 React 直接設置了
onerror
的屬性。我好興奮啊!寫 React 這麼久第一次知道這個特性,在攻擊 React app 上又多了一個攻擊面。