LA CTF Writeup
TL;DR
這次與 ICEDTEA 的各位拿到 LACTF 第 9 名 開心

以下是我有打的題目,大部分都是有依靠 AI 如果我理解有錯歡迎私我指正 感謝
DC: tbk_lteaar
LLM 真的好強
Reverse
the-fish
Solver: LemonTea
sorce:
1 | import sys |
這是一個自制的 esolang
Interpreter
1 | class Interpreter: |
這個自制的 esolang 的 Interpreter
- 他是個 2D 平面
- 有(x, y)位置
- 一開始在(-1, 0),方向往右
- 指令會:
- 照方向移動
- 碰到邊界就 wrap around
Stack-based + 多堆疊 + Register
它有:
self._stack:目前使用的 stack
self._stack_stack:stack 的 stack
self._register_stack:每個 stack 對應一個暫存器
特殊指令:
1 | 指令 功能 |
數值:
0-9跟a-f: 推入 hex 值+ - * % ,: 運算值
控制:
^ v < >: 改方向/ \ | _ #: 鏡像反射x: 隨機方向!跳過下一個指令?pop 值為 0 才跳過
字串
'或": 進入 string mode
I/O
o: pop -> 輸出字元i: 讀一個輸入字元n: 檢查 flag;: 結束程式
好解釋完程式了
1 | elif instruction == "n": |
判讀 flag 的邏輯
他給了一個巨大整數
看 code 去理解整數怎麽產生的 推回去就好
exploit.py
1 | int_num = 996566347683429688961961964301023586804079510954147876054559647395459973491017596401595804524870382825132807985366740968983080828765835881807124832265927076916036640789039576345929756821059163439816195513160010797349073195590419779437823883987351911858848638715543148499560927646402894094060736432364692585851367946688748713386570173685483800217158511326927462877856683551550570195482724733002494766595319158951960049962201021071499099433062723722295346927562274516673373002429521459396451578444698733546474629616763677756873373867426542764435331574187942918914671163374771769499428478956051633984434410838284545788689925768605629646947266017951214152725326967051673704710610619169658404581055569343649552237459405389619878622595233883088117550243589990766295123312113223283666311520867475139053092710762637855713671921562262375388239616545168599659887895366565464743090393090917526710854631822434014024 |
Flag: lactf{7h3r3_m4y_83_50m3_155u35_w17h_7h15_1f_7h3_c011472_c0nj3c7ur3_15_d15pr0v3n}
flag-finder
Solver LemonTea
題目給了一個網頁進去

嗯 看來沒什麼 點開 F12
看來他給了一個 index.js
1 | const fullInput = document.getElementById('fullInput'); |
稍微看了一下
在這 index.js 有個爆炸長的 Regex(也就是Flag)
他的 box 是用 # 代表黑 跟 . 代表白
那就寫個腳本去把圖畫出來就好
1 | import re |
Flag: lactf{Wh47_d0_y0u_637_wh3n_y0u_cr055_4_r363x_4nd_4_n0n06r4m?_4_r363x06r4m!}
HELM-HELL
Solver: LemonTea
這題給了一個 .tpl file
1 | {{- define "tideInlet200" -}} |
1 | 以上是節錄 |
1 | #!/usr/bin/env python3 |
output:
1 | ~/Documents/CTF/LACTF/helm hell/helm-hell> python extract_chars.py |
Flag: lactf{t4k1ng_7h3_h3lm_0f_h31m_73mp14t3s}
lactf-1986
Solver: LemonTea
這題題目給了一個 IMG檔案
先 binwalk
解出了一個 .exe檔案
1 | ~/Documents/CTF/LACTF/extractions/CHALL.IMG.extracted/0/rootfs> file CHALL.EXE |
嗯 MS-DOS 的 binary
好開始分析
那整個 binary 是個 flag checker
計算初始狀態(Hash):程式會先將整個輸入字串傳入一個雜湊函數,計算出一個 20-bit 的數值(Seed)
驗證過程:
程式使用上述計算出的 Seed 作為初始狀態
它進入一個迴圈,針對輸入字串的每一個字元進行檢查
關鍵步驟:在比對第 i 個字元之前,它會先呼叫一個狀態更新函數
更新後的狀態的低 8 位元會與程式內建的加密數據進行 XOR 運算
運算結果必須等於使用者輸入的第 i 個字元
exploit
1 | TABLE = bytes.fromhex( |
Flag: lactf{3asy_3nough_7o_8rute_f0rce_bu7_n0t_ea5y_en0ugh_jus7_t0_brut3_forc3}
Web
glotq
Solver: LemonTea
middleware.go
1 | package main |
在 這段 middleware.go 裡 request body 被用 JSON 解析
handlers.go
1 | package main |
然而在 handler.go 則是用 XML 解析
並且他是使用
xml.Unmarshal
那在 Go 裡 這個東東會無視 JSON 所以他只吃 XML
那 問題點就是在同一個資料被解析兩次 .w.
payload:
1 | curl -X POST "http://TARGET:8080/xml" \ |
Flag: lactf{PoLY9LOt_TH3_Fl49}
clawcha
Solver: LemonTea
app.js
1 | const crypto = require('crypto'); |
看了一下 我們要拿到 owner(也就是 r2uwu2)
但 password 是 secret
看看 secret 產生的過程
1 | const secret = process.env.SECRET || crypto.randomBytes(16).toString('hex'); |
嗯 是隨機的 好看來沒用
看到這個
1 | app.use((req, res, next) => { |
這段 code 在幹嗎
簡單來講:先從 signed cookie 讀取 username 並確認是字串,並且拿這個 string 去當 key 去查 users 這個 Map 找到後就把對應的放進 res.locals.user
那點就在這了 因為這段已經被 json 解析過了
那我們只要控制解析過後的字串就能 get owner 了
並且我使用的 username 是 r2u\\u0077u2 因為在解析的當下 \u0077 會被解析成 w
payload1
1 | curl -s -X POST https://clawcha.chall.lac.tf/login -H 'Content-Type: application/json' -d '{"username": "j:\"r2u\\\u0077u2\"","password":"pass"}' -c '/tmp/c.txt' |
payload2
1 | curl -s -X POST https://clawcha.chall.lac.tf/claw \ |
c.txt
1 | cat /tmp/c.txt |
Flag: lactf{r2u_wu2_json_cookie_exploit}
BOBLES-AND-NARNES
Solver: LemonTea
server.js
1 | import express from 'express'; |
這題是運用 Bun SQL db() 在批量插入時 會以第一個物件的 Key 作為整個資料表的 schema
這也就是說,如果第一個產品物件沒有 is_sample 這個欄位,db() 會完全忽略這個欄位,即使後面物件有 is_sample
結果:flag 產品的 is_sample=1 也會被忽略,資料庫中存的實際值是 NULL
並且在餘額檢查使用 !+product.is_sample 這會導致
NULL被當作 sample 因此可以繞過檢查加入購物車1sample 被過濾
結賬的檢查:- 從 DB 取出
is_sample但前面已經讓is_sample=NULL了,所以會誤以為是非 sample 就可以 get flag 了
1 | import requests |
Flag: lactf{hojicha_chocolate_dubai_labubu}