被 web 題電得亂七八糟,基本上什麼都沒解出來。題目的品質都很不錯,學到很多新東西,值得記錄一下。
關鍵字:
Electron relaunch to RCE
利用 Python decorator 執行程式碼
透過特殊檔名讓 Apache 不輸出 content type header
GIF + JS polyglot
繞過 SQLite 不合法欄位名稱
JS 註解 <!--
superjson
babyelectron(21 solves)
給你一個 Electron 的 app,目標是 RCE,有一個 bot 會用 app 訪問你的頁面,然後要先找到一個 XSS,這段就先不提了。
這題該開的 security 設置都有開,關鍵是在 preload 裡面有一段這個:
const RendererApi = {
invoke: (action, ...args) => {
return ipcRenderer.send("RELaction",action, args);
},
};
// SECURITY: expose a limted API to the renderer over the context bridge
// https://github.com/1password/electron-secure-defaults/SECURITY.md#rule-3
contextBridge.exposeInMainWorld("api", RendererApi);
在另外一個 JS 則有這樣一段:
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
app.RELbuy = function(listingId){
return
}
app.RELsell = function(houseId, price, duration){
return
}
app.RELinfo = function(houseId){
return
}
app.RElist = function(listingId){
return
}
app.RELsummary = function(userId){
return
}
ipcMain.on("RELaction", (_e, action, args)=>{
//if(["RELbuy", "RELsell", "RELinfo"].includes(action)){
if(/^REL/i.test(action)){
app[action](...args)
}else{
// ??
}
})
看起來沒什麼用,因為那些方法都沒實作。
但重點是你送 relaunch 的指令進去也會 match 到,所以你可以執行 app.relaunch,在 relaunch 的時候可以指定執行檔位置,就可以 RCE。
然後在 Python 裡面可以用 __builtins__ 拿到內建的所有東西,感覺有點像是 js 的 global 那樣,可以看出有哪些東西可以用。
用 dir() 可以列出所有屬性,所以可以自己寫一個遞迴去找出 list,像這樣:
visited = set()
def search(obj, path):
for name in dir(obj):
item = getattr(obj, name)
new_path = path + "." + name
if (type(item) == list):
print(new_path)
return
if type(item) not in visited:
visited.add(type(item))
search(item, new_path)
search(__builtins__, "__builtins__")
最後就會找到 __builtins__.copyright._Printer__filenames 這個存在於 global 的 list。
被 web 題電得亂七八糟,基本上什麼都沒解出來。題目的品質都很不錯,學到很多新東西,值得記錄一下。
關鍵字:
<!--
babyelectron(21 solves)
給你一個 Electron 的 app,目標是 RCE,有一個 bot 會用 app 訪問你的頁面,然後要先找到一個 XSS,這段就先不提了。
這題該開的 security 設置都有開,關鍵是在 preload 裡面有一段這個:
在另外一個 JS 則有這樣一段:
看起來沒什麼用,因為那些方法都沒實作。
但重點是你送
relaunch
的指令進去也會 match 到,所以你可以執行 app.relaunch,在 relaunch 的時候可以指定執行檔位置,就可以 RCE。DC 裡面 zeyu2001 提供的 payload:
Sudistark 的 writeup:https://github.com/Sudistark/CTF-Writeups/blob/main/2022/Hack.lu/babyelectron.md
Culinary Class Room(6 solves)
這題限制你只能幫一個 class 加上最多 250 個 decorators,而且不能有參數,目標是要能夠執行任意程式碼拿到 flag。
作者的解法是找到一個 list 然後往裡面 push 很多數字,最後丟到 bytes 以後再丟到 eval 去執行,例如說以下程式碼會往
copyright._Printer__filenames
push 112 這個數字底下是來自 Arusekk 在 DC 貼的 payload:
上面的就是在做:
因為對 Python 極度不熟,所以來惡補一下。
__doc__
可以拿到一個 method 的文件,要在 source code 裡面宣告,像這樣:原來 Python 有這麼好用的功能,看起來在開發上滿實用的,要輸出成文件什麼的應該比較容易
然後在 Python 裡面可以用
__builtins__
拿到內建的所有東西,感覺有點像是 js 的 global 那樣,可以看出有哪些東西可以用。用
dir()
可以列出所有屬性,所以可以自己寫一個遞迴去找出 list,像這樣:最後就會找到
__builtins__.copyright._Printer__filenames
這個存在於 global 的 list。而上面貼的解法,找到數字之後用
@copyright._Printer__filenames.append
丟進去陣列,回傳值是None
,然後利用"abc".format(None)
還是 "abc" 的特性,就可以再把 input 變成想要的字串,然後用 len 去拿到數字。YummyGIFs(5 solves)
可以上傳一張 gif(有經過嚴格檢查,要真的是 gif 檔)並搭配標題跟敘述,敘述會過濾之後 render 在畫面上:
看起來很嚴格,但其實可以用未閉合的標籤繞過,像這樣:
<script src="" p="
,所以還是可以插入任意 tag。接下來問題就是要怎麼讓 src 合法,因為有 CSP self 的關係,所以我們要產生出一個又是 GIF 但又是合法的 JS code,但儘管產出了,因為 content type 是 image/gif,所以瀏覽器還是會報錯,會出現:
而解法就是想辦法不要輸出 content type 就好。
因為這個 content type 是 Apache 給的,可以用檔名來繞過,例如說檔名是
..gif
,就不會給 content type,可參考:https://twitter.com/YNizry/status/1582733545759330306這招感覺滿值得筆記下來的。
至於怎麼產生 gif + js polyglot,可以參考:https://gist.github.com/ajinabraham/f2a057fb1930f94886a3
順便在這篇順便筆記一下 png 的:PERSISTENT PHP PAYLOADS IN PNGS: HOW TO INJECT PHP CODE IN AN IMAGE – AND KEEP IT THERE !
foodAPI(4 solves)
這題的核心程式碼就這一段:
id
會是個 object,你有完全的掌控權,但是不支援 array 跟 nested object,只能傳單純的物件進去。目標是 SQL injection。
這題是我看最久而且最認真的一題,直接開 Chrome debugger 進去 trace code,底下簡單講一下內部的運作。
首先會把你傳進去的 object 轉成底下這樣的形式:
然後丟給 this._translator.translateToQuery 去產生出弄好的 SQL query,接著用神秘的字串分割去切,看有沒有 sub query,然後丟到 SQLite 裡面,部分程式碼如下:
切字串的地方之前有出過事,改了之後也還是會出事,但在這題好像無關緊要:https://github.com/eveningkid/denodb/pull/241
這邊產生出來的 query 已經是完整的 SQL query 了,也就是說參數綁定這件事情並不是丟到 SQLite 去做,而是直接用 JS。
那這個完整的 SQL query 到底是怎麼出來的呢?
首先,你的東西會被丟進 query builder 去,執行像這樣的東西:
而那個
queryBuilder.where
裡面,基本上就是根據你傳進來的東西去做事,例如說如果我傳:{field:"id", operator:"=", value:"hello"}
,最後就會執行到:所以最後轉換成字串,就是根據這個
this._statements
去弄。首先它會先根據你這些 where 組出語句來,怎麼個組法呢?就是把 column 用 backtick 包起來,然後把值變成
?
,像這樣:這個所謂的「包起來」,程式碼在:https://github.com/aghussb/dex/blob/1.0.2/lib/formatter.js#L274
產生完 SQL query 以後,開始做 data binding,程式碼大概是這樣:https://github.com/knex/knex/blob/2.3.0/lib/execution/internal/query-executioner.js#L6
把
?
取代成字串,然後取代之前會先 escape,escape 的內容就是外面加單引號,然後把字串本身的單引號變成兩個單引號。看起來沒什麼問題,但是 deno 的 lib 忘記對欄位名稱的
?
做 escape 了,所以如果你傳:{"id":"1", "?": "A"}
,最後出來的 SQL 會是:而 bind 完之後就會變成:
你會發現 A 那邊可以做 SQL injection,只要先閉合那個反引號就行了。
但問題是這樣會產生不合法的欄位名稱,因為裡面一定有個單引號,像這樣:
會出現:
當初做到這邊就卡住了,大概就兩條路:
答案是後者。
底下這兩種都不會出錯:
不要問我為什麼,我也不知道,感覺是某種語法上的 bug(或 feature XD)
弄出 SQL injection 以後就弄個 time-based 的 query,然後用 xsleak 去測時間即可。或也可以像 terjanq 弄成 error-based 的,效率會再高一點。
其他人的 writeup:
HTPL(3 solves)
這題是一個自製的 AST,用 HTML 的方式來組合出 JS,例如說:
就會被翻譯成
"hello"
。目標是偷到 cookie,所以要能夠執行 XSS。這題看很久但沒什麼想法,我有想過是不是透過一些數學運算可以跳脫字串之類的,但沒找到
\
,想用註解也沒看到*
可以用。賽後發現想法近了,但忘記 HTML 的註解
<!--
也可以用。用小於 + not + 減法就可以湊出註解的符號,像這樣:就會翻譯成:
最後的分號會被弄掉,於是可以結合下一行的
[]
變成存取屬性,像這樣:會翻譯成:
也就是
const $a$="x"["toString"]
做到這邊好就簡單了,再繼續串下去拿到 function constructor 之後再呼叫即可,像這樣:
會變成:
terjanq 的解法更短,直接利用 iframe + name 會拿到 window 的特性,去拿 iframe 裡的 eval(那個 if 拿掉也沒差):
程式碼會是:
JaaSon(6 solves)
同場加映一題 misc 的 JS 題,這題你可以給一個 json string,會被丟到 superjson 去。
用的雖然是有 prototype pollution 漏洞的版本,但是已經先用
Object.freeze(Object.prototype)
把 prototype 鎖起來,沒有 prototype pollution 可以用了。這題還沒時間研究,但跟 superjson 內部運作的機制有關,可以透過
referentialEqualities
這東西去指定一些值,例如說:就會執行
products[0].brand = brands[0];
,看來應該是想透過這個解決 deep clone 時的 reference 問題。詳情可以參考:Remote Code Execution via Prototype Pollution in Blitz.js,裡面解釋得比較完整。
其餘細節我就沒有再研究了,但看起來是透過這個功能把物件的一些東西換掉,
底下附上 szymex73 在 DC 貼的 payload:
比起上面這個,我隊友 pew 的 payload 似乎比較好懂:
後記
這次的題目都很有趣而且很新穎,例如說 Python 那題只用 decorator 做出任意程式碼執行就很酷,或是 foodAPI 直接考一個 denoDB 0-day,也是滿猛的。
SQLite 的神秘語法也是大開眼界,期待之後有人 po 出 write-up,從原始碼去解釋一下是哪一段有那個功能,到底是 feature 還是 bug。
而 HTPL 其實最後的考點還是 JS 的註解
<!--
,但被包裝起來以後就不是這麼容易發現,這種「拆開之後發現是自己熟悉的東西」,以題目來說我覺得滿理想的。例如說像是 gif 那題,如果我沒解出來,我只會覺得我知識量不足,不知道
..gif
可以繞,或覺得看 code 能力不足,沒辦法看太底層。但像是 HTPL 這題,沒解出來但發現原來知識點是自己知道的,就會覺得題目包裝得十分巧妙。突然覺得跟以前一些競程的題目有點像,有些題目解不出來是因為我真的沒學過那演算法,但有些題目層層拆解之後發現不會太難,只是包裝得很好,就會覺得「哇,這出題者好猛」
話說 terjanq 在我心目中是 CTF 界中前端、瀏覽器以及 JS 相關題目的 GOAT,感覺只要是這類型的題目,他就一定解得出來,真的很猛。
當然,其他強者也不是蓋的,每次都會發現難題幾乎都是固定那幾個 id 解掉XD