You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
app.get("/:t/:s/:e",(req: Express.Request,res: Express.Response): void=>{consts: number=Number(req.params.s)conste: number=Number(req.params.e)constt: string=req.params.tif((/[\x00-\x1f]|\x7f|\<|\>/).test(t)){res.end("Invalid character in book title.")}else{Fs.stat(`public/${t}`,(err: NodeJS.ErrnoException,stats: Fs.Stats): void=>{if(err){res.end("No such a book in bookself.")}else{if(s!==NaN&&e!==NaN&&s<e){if((e-s)>(1024*256)){res.end("Too large to read.")}else{Fs.open(`public/${t}`,"r",(err: NodeJS.ErrnoException,fd: any): void=>{if(err||typeoffd!=="number"){res.end("Invalid argument.")}else{letbuf: Buffer=Buffer.alloc(e-s);Fs.read(fd,buf,0,(e-s),s,(err: NodeJS.ErrnoException,bytesRead: number,buf: Buffer): void=>{res.end(`<h1>${t}</h1><hr/>`+buf.toString("utf-8"))})}})}}else{res.end("There isn't size of book.")}}})}});
跟著隊伍 Water Paddler 一起參加了 LINE CTF 2022,在隊友的 carry 之下拿了第七名,這次只有一題有幫上一點忙,其他都被隊友解掉或是卡死。這篇簡單記一下每一題的解法,大部分都參考自 LINE CTF 2022 Writeups by maple3142。
gotm(96 solves)
這題被隊友解掉所以沒仔細看,不過賽後看其他 writeup 是 go 的 SSTI,出現在這裡:
之前沒碰過 go 的 SSTI,稍微筆記一下,可以用 {
{.}}
把傳入的物件整個 dump 出來,順便附幾個參考連結:Memo Drive(42 solves)
先附上關鍵程式碼:
這題的 flag 在
./memo/flag
底下,所以只要想辦法讓上面那一段的 path 可以讀到 flag 就勝利了。隊友最後用這個 payload:
/view?id=flag;%2f%2e%2e/;
,因為對 python 太不熟,所以起個簡單的 server 來觀察一下:先來看一下隊友的 payload 會怎樣:
/view?id=flag;%2f%2e%2e/;
request.url
會直接是 raw URL,沒有 decode 過,然後request.url.query
也是沒 decode 過的版本,到了request.query_params
的時候則是被解析成了兩個 params:看起來是因為分號
;
的關係,所以就算不用&
也可以創造出兩個 params。而最後在
request.query_params.keys()
的時候被 decode,所以最後合起來就會是./memo/id..//flag
。不過在 Discord 上看到其實這樣就好了:
id=flag;/%2e%2e
,結果會是:接著也在 Discord 看到另一個不同的解法(來自 bbangjo#3967),是利用 Host header:
就會產生神奇的結果:
雖然
request.url.query
整個變不見了,但是request.query_params
卻還是有東西,因此就繞過了針對request.url.query
的檢查。根據他的說法,因為
request.url
是從 Host header 構造而來的,我們可以翻一下程式碼來驗證,如果沒找錯的話應該是在這:starlette/datastructures.py#L38:因為 Host 被加了個
#
,所以後面的 query string 就被當成 fragment 來解析了,而不是 query string,所以request.url.query
就會是空的。那為什麼
request.query_params
有東西呢?因為它是直接拿最原始的 query string,而不是request.url.query
,在這邊:starlette/requests.py#L116這真的是要看 source code 才會發現這種差異。
2022-03-29 補充:
感謝 Zedd 提醒,把
;
當作&
來看的行為跟 Python 版本有關,因為會引起 cache poisoning 的關係,在較新的版本中都已經修復了,而挑戰時使用的版本是 3.9.0,所以才有這問題,而我在本機重現時用的也是還沒修復的版本。漏洞編號為 CVE-2021-23336,詳情可看這裡:urllib parse_qsl(): Web cache poisoning - semicolon as a query args separator。
bb(27 solves)
程式碼很短:
基本上就是要做到控制環境變數之後 RCE,這讓人自然而然會想到前陣子 P 牛發表的這篇:我是如何利用环境变量注入执行任意命令,裡面提到可以藉由控制
BASH_ENV
來執行命令。不過比較麻煩的地方是 a-zA-Z 都不能用,所以要在不能用英文字母的狀況下寫出指令來讀 flag 並回傳到自己 server。
聊天室有人給了一個類似題目的連結可以參考:34C3 CTF / Tasks / minbashmaxfun / Writeup,看了開頭給的 writeup 也才發現原來可以這樣用:
靠這樣就可以繞開限制,不用到字母,bash 真是博大精深。
在 Discord 看到有人貼這串,值得參考跟筆記一下:好讀版,推特原串:https://twitter.com/DissectMalware/status/1023682809368653826
online library(19 solves)
這是個可以讀取特定檔案範圍的網頁,重點在這一段:
第 path 的地方放上
/%2e%2e%2f/0/12345
就可以 path traversal 然後任意讀檔一下,但問題是要讀哪裡。在隊友的幫忙下讀了
/proc/self/mem
,就是現在 node process 的記憶體,至於讀哪段要從/proc/self/maps
去找,怎麼找我就不知道了。然後因為有個 endpoint 會把參數放到 memory 中,所以可以先用那個 endpoint 去放你的 payload,接著因為這題讀檔有給 offset 的關係,找到記憶體中的 payload 把 offset 設定好,丟給 bot 以後就 XSS 了。
不過根據賽後討論,似乎是因為 flag 在 cookie 中,而 bot 送 request 到 server 時會帶 flag,所以這段 flag 也會出現在記憶體中,因此直接讀記憶體也可以找到 flag,不用 XSS。
Haribote Secure Note(7 solves)
這題卡了一整天,到最後依舊沒解開,so sad QQ
這題可以設定一個暱稱,最多 16 個字,然後可以新增 note,有 title 跟 content,顯示筆記的頁面關鍵程式碼在這裡:
還有接近結尾的這段:
前面那邊給了我們 16 個字的 JS injection,最後面 notes 那裡則是可以用
</script>
來跳離標籤,是 HTML injection,而這題的難點在於 CSP 很嚴:因為有 nonce,所以
unsafe-inline
沒作用,而unsafe-eval
沒開所以也沒辦法動態去執行程式碼。當初卡很久之後我有一個想法是我們可以用 HTML injection 插入一個表單
<form id="f">
,然後就可以對 admin CSRF,目的是去改 admin 的暱稱,因為在另一個頁面 profile 是沒有 CSP 的,而且同樣可以注入:nickname 的部分可以設定成:
";f.submit();"
之類的,就可以送出表單。改完之後再去造訪 profile 頁面,在那個頁面執行 XSS。但最大的問題是
"onfocus=eval(name)
有 20 個字元,超過了界線所以無法成功(而且還要想一下 name 要怎麼設定)。賽後看了其他人的解答,主要有三種。
第一種來自 Super HexaGoN,是利用一個神奇的 script data double escaped state,把兩個注入點中間的東西都註解掉,就可以在有 nonce 的 script 裡面執行程式碼。之前從沒看過這個,以後再來研究一下。
第二種是利用 import 不會被 Trusted Types 檔的特性,底下 payload 來自 maple3142:
第三種則是利用 iframe,在其他頁面執行程式碼(來自 eskildsen#8025):
第三種是我唯一覺得自己有可能想到的,因為其他兩個我都不知道。
話說
";f.eval(p+"");"
跟<!--<script>"}/*
恰巧都是 16 個字,我猜其中一個應該是非預期解,這就是 CTF 好玩的地方XD然後這題真的很有趣而且很值得學習,三種解法都是完全不同的思路。
喔對了,然後 maple3142 的 writeup 解決了我一個疑惑,那就是為什麼這一題的 template 都不會 escape,原來是因為 flask 預設只會 escape HTML/XML/XHTML,難怪我沒看到什麼設定。
title todo(6 solves)
這題基本上就是個上傳圖片的網站,上傳完會拿到一個 url,接著可以給 title 跟 image url 新建一個 post。
flag 則是用 admin 身份造訪時會放在網頁的最 footer,而且有著奇怪的格式:
LINECTF{([0-9a-f]/){10}}
然後在顯示圖片的網頁有個地方沒有用雙引號包住:
雖然看起來是很小的一點,但其實整題的解法都是從這邊延伸出去的。從這邊不難看出我們可以控制 img 的任何屬性,不過我在這邊卡了頗久,想說可以控制又怎樣,沒辦法跳離 img 就不能 XSS。
然後經過隊友提醒才想到 STTF 的 xsleak,透過 img 的 lazy loading 來偵測是否有 scroll 的行為,所以只要 title 用很長,把 img 推下去,再加上
loading=lazy
的屬性,就可以搭配 STTF 來 leak 一個 byte。不過這題還有一點要注意,就是 CSP:
CSP 繞不開,所以就算
src
可控,也沒辦法設置外面的圖片。因此這題加上了另一個機制:cache,可以根據 response header 來決定一個圖片的 cache 是 miss 還是 hit,所以我們只要上傳一張新的圖片丟給 bot,過幾秒再去看他的 response header,如果是 hit 就代表 bot 有訪問圖片,代表 SSTF 有成功。照著這個概念寫一個 exploit 就好:
另外,maple3142 的 writeup 又解決了我一個疑惑,那就是為什麼 flag 要有那些
/
?原來是因為 Chromium 為了避免這種 xsleak,所以在判斷 SSTF 的時候一定要匹配到整個單字才會 scroll。舉例來說,如果頁面上有這串字:
Hello world
,你 text fragment 指定He
,是不會理你的,要Hello
才會,這也是為什麼這題要用/
來分割,因為沒分割的話就沒辦法一個字一個字來 leak。me7-ball(2 solves)
這題看起來好像跟 crypto 比較有關就沒仔細看了,直接貼 Super HexaGoN 的 writeup:https://gist.github.com/mdsnins/2912b9656c837e5190364136b307c682
The text was updated successfully, but these errors were encountered: