前言
這是我的第一次CTF,屬於團隊類型的CTF
雖然這場比賽已經快湊齊BadCTF Bingo了
但我還是打得很爛 writeup也寫得很爛
也是獲的了第12名 (一個人爆砍27題
還是值得紀念的 畢竟是第一次
writeup有部分是我隊友寫的
Welcome

題目說已經給過了
開控制台檢查
得到flag FhCTF{S3n1ty_Ch3ck1ng....😝}
Misc
Christmas Tree

查看檔案
典型的霍夫曼樹結構(Huffman tree)
1 | import json |
得到flag FhCTF{Hoffman_is_a_great_Christmas_tree}
駭客的密碼食譜

這道題目的解法是將食材的重量/數量轉換為 ASCII 字元
並依照製作方法的順序排列最後將字串反轉
例如
cake flour 125 = }
caster sugar 110 = n
可得原始字串 }nuf_is_gnikooc{FTChF
用的堆疊(Stack)方式還原FhCTF{cooking_is_fun}
得flag FhCTF{cooking_is_fun}
笑話大師

點開連結可看到一客製化的Gemini
原本我以為是普通的prompt injection,結果死活解不開
突然靈光一閃,既然這是個Gemine的客製化AI那在Gem裡面一定有出題者設定的提示詞
建立副本
得到flag FhCTF{thisi_Prompt_Injection}
分享圖庫

非常典型的php Webshell
加上是給新手打的所以直接一套php木馬製作流程
1 | convert -size 1x1 xc:white legit.png |
然後裡面的偵測方式為==判斷文件頭==是否為png所以直接副檔名可以改php上傳
訪問/uploads/shell.php?cmd=printenv flag(原始檔有寫flag在哪)
得到flag FhCTF{png_format?Cannot_stop_php!}
Python Compile


很特別真的不會執行
這題我卡了很久
發現不是普通的SSTI(服器端模板注入)

這行被我朋友發現如過修改成裡面擁有的檔案就能顯示其內容
這是對的思路
我一開始也有發現但被ai誤導
原本以為是flag.txt flag這種檔案結果不出來便被ai誤導成其他解題路線(畢竟是雜項
直到修改為/etc/passwd
成功用報錯輸出其內容
所以將其修改為/proc/1/environ
這載入容器時的環境變數存放的檔案
輸出
1 | UV_TOOL_BIN_DIR=/usr/local/binHOSTNAME=54f86cd99223HOME=/rootUVICORN_APP=main:appGPG_KEY=7169605F62C751356D054A26A821E680E5FA6305PYTHON_SHA256=16ede7bb7cdbfa895d11b0642fa0e523f291e6487194d53cf6d3b338c3a17ea2PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binUVICORN_HOST=0.0.0.0PYTHON_VERSION=3.13.11UVICORN_PORT=8000PWD=/appFLAG=FhCTF{N0t_s4f3_t0_ou7put_th3_err0r_m5g}^ |
得到flag FhCTF{N0t_s4f3_t0_ou7put_th3_err0r_m5g}
分享圖庫 Revenge
分享圖庫的進階題
這我真不會解,檔案照樣可以上傳但他被只當成png來解讀
1 | �PNG IHDR%�V�PLTE������ pHYs���+ IDAT�c`�qd�IEND�B`� |
又是我的朋友
他找到了一個超級搞笑的解法
題目有漏洞(當然也可能是這麼設計的畢竟是雜項:)))
訪問容器的位置就全部輸出了
到現在還沒修應該就是這麼解的(上一個分享圖庫也能這麼解
Web
Welcome to Cybersecurity Jungle

基於新手題與題目敘述先查看cookie
cookie 名稱 aXNGbGFnU2hvdzJ1 進行 Base64 解碼:
解碼結果:isFlagShow2u
44G144GJ44O844KL44GZ Base64 解碼後得到日文假名:
解碼結果:ふぉーるす(是日文假名寫法的 false)
將日文假名的 true轉 Base64 後,得到:44Go44GF44KL44O8
修改cookie後重整畫面即可得到flag
(跟原本的一樣嗎沒關係他只是把flag顏色條成空白的
取得flag FhCTF{Th3_e553nc3_of_pr0gramm1n6_is_ind3p3nden7_of_the_languag3_u53d}
INTERNAL LOGIN

最基礎的SQLInjection
取得flag FhCTF{SQL_1nj_42_Success}
The Visual Blind Spot

大致理解題意為需要輸入正確的RGB色調才會給flag
先看原始碼

兩處flag都是假的
找到關鍵函式
1 | const _secureStr = loadSystemParams(); |
也就是說:
真正被加密顯示在畫面上的文字,來自 loadSystemParams()
sys-config 元資料
1 | <div id="sys-config" |
這串數字就是 明文來源(但被簡單編碼)
原始程式碼
1 | let charCode = (n / 3) - 13; |
直接人工還原
將每個數字套用公式 (n / 3) - 13
得到字串
1 | FhCTF{Stn3am_C1ph3p} |
提一嘴另一解法
1 | window.onload = function() { |
這是金鑰生成的方式
hex ==32==也就是50
偏移後得到100,50,200
依序輸入就可以取得flagFhCTF{Stn3am_C1ph3p}
Web Robots

看到題目名稱就知道要去訪問/robots.txt
看到有趣的網址/secret
嗯 自動導向一個什麼都沒有的網址/secret/index.html
直覺 (通靈) 告訴我一定在flag.txt
取得flag FhCTF{r0b075_4r3_n0t_v15ible_in_tx7}
Doors Open

點開寫著The Door is OPEN!!!FIND THE DOOR
查看原始碼
裡面寫提示看robots
訪問doors
出現開門動畫並提示並非正確的門
1 | const door = document.getElementById('door'); |
查看原始碼
網址顯示/doors/1
根據原始碼推論得知要尋找正確的門號
並且我可以直接訪問api/door/XXX以節省載入動畫的時間
其中門號被限制為5000
用腳本訪問0-5000
1 | BASE="https://15e47f56.fhctf.systems/api/doors" |
嗯 都沒有爛題 (誤
我摸了一下水晶球
訪問-1試試
1 | {"correct":true,"message":"這是正確的門!\nFlag: FhCTF{IDOR_get_the_s3cr3t_infom47i0n}"} |
原來藏在無法訪問的最後一個
取得flag FhCTF{IDOR_get_the_s3cr3t_infom47i0n}
Templating Danger

又是留言板(冷不丁的抖了一下
說實話我真的很不會注入
嘗試了一下payload
1 | {{7*7}} |

應該就是XSS(笑死我沒看原始檔好孩子不要學
實際上是SSIT這題我錯了兩得多小時{{7*7}}不行是因為裡面有寫偵測{}的程式
所以改成Unicode編碼\u007b
1 | \u007b\u007b cycler.__init__.__globals__.os.environ.get('FLAG') \u007d\u007d |
取得flag FhCTF{T3mpl371ng_n33d_t0_b3_m0r3_c4r3full🥹}
Documents


提示說http標頭已經告訴我的一切(那是什麼
由於後端使用FastAPI先嘗試存取其預設OpenAPI文件以了解實際路由配置
訪問/openapi.json
看到有趣的路徑
嗯 他耍我(欠扁
重看一次/openapi.json
1 | "/flag.html": { |
發現關鍵
1 | "description": "Referer Checker\nWe will check you whether coming from localhost.app:8000/index.html, with secure HTTP protocol." |
合理推測要Referer改為localhost.app:8000/index.html

取得flag FhCTF{URL_encod3d_m337_p47h_d15cl0sure😱😱}
SYSTEM ROOT SHELL

熟悉的問題(在台科社課看過
我還以為要在系統裡找flag
結果似乎被設定成只要普通的判斷式而非真實運行的指令(看來主辦方很怕
取得flag FhCTF{RCE_Success_v3}
LOG ACCESS

跟上題很像是一種判斷式的路徑穿透題
(我通靈出來的
1 | function access() { |
1 | const check1 = input.split('.').length > 3; |
第一行是判斷.是否大於3個
第二行是判斷輸入字串是否含有flag
只要條件滿足就會輸出flag(甚是flag…也行:)))
然後flag其實也寫在裡面
1 | const _h = [70, 104, 67, 84, 70].map(c => String.fromCharCode(c)).join(''); |
這四個值解出來分別為FhCTF Path_ 535 Tr4v
根據這行
1 | const final = _h + "{" + _c1 + _c3 + "_" + _c2 + "}" |
也可得知flag為FhCTF{Path_Tr4v_535}
Pathway-leak

有給一個文檔
1 | [2025-11-02 20:25:38 UTC] 213B STANDARD devops/internal_api.json |
flag顯示在secret_admin/flag.txt中
嘗試修改路徑
無法直接請求
1 | currentFileTitle.textContent = filename; |
看到有趣的原始碼
理解了嘗試跨 tenant 請求
取得flag FhCTF{p4th_tr4v3rs4l_w3_w4n7_t0_av01d}
KID


一開始並沒有cookie
取的通行證後發現cookie為JWT格式(嘔 這不純潔
1 | eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImRlZmF1bHQucGVtIn0.eyJ1c2VyIjoiZ3Vlc3RfdXNlciIsInJvbGUiOiJndWVzdCJ9.S4-YIB8_-sXb1sLJAkoDPr1HHEO7r8E4DPjFkxi3vJyYDlHiJKFEkw3L7X0yBOvxuBJ-PMXhPIgc1I9phkNX9w9cLEMUDZtsdqVh1BMhux-H8g0S9HHaQ3ZFcMb_f9WZpgdM1RQ-i-dlCxxzsHus2E13sJc2ITHASsw4xJz-wtdaN_ME3EF_conBM_P5mF_fM3GT-7zvfiUtcu845FREG6BZY_Z7ji_S6A8R0jN200ziB4s9qkd2TnxAuiCAtANV9MnPJZYFibwZOQspXf3cGcyCeO307yiQNljQfZlxWGRt8f3V1rlXa2UXx2rEnB_85wRQbMmPchvm9p7nN8O_cA |
有提到可以允許HS256格式JWT解碼如下:
1 | { |
經典的JWT Algorithm Confusion風險
其實我這題我解很久(被線上JWT坑的
1 | import jwt |
使用者可控制 kidkid 決定驗簽金鑰來源fallback 至 HS256
驗簽時使用 /dev/null → 空 key
則只要攻擊者在簽署 JWT 時:
1 | HMAC-SHA256(secret="", message="header.payload") |
而伺服器驗簽時 也使用同一個空secret則:
1 | signature_attack == signature_server |
驗簽必然通過
取得flag FhCTF{Th3_k1d_u53d_JWT_t0_tr4v3rs3_p4th5}
Something You Put Into

其實跟上一題一樣是JWT的問題
但其實我不會正規解法
雖然很丟臉但我在電腦前做了1個小時還是解不開
結果題目的檔案有問題
他把flag直接放在裡面了
嘻嘻我有存
(它晚上被緊急?下架了
取得flag FhCTF{🐷B3_c4r3ful_y0ur_SQL_synt4x🐷}
Reverse
簡易腳本閱讀器

研究server程式後發現script[4] 會被使用者輸入直接覆蓋(script[ip] = user_input)
支援的指令只有兩種特別處理:
USER_INPUT → 要求輸入
JUMP <數字> → 跳到指定行號
其他行直接輸出([Line {ip}] {line})
global script → 腳本內容在整個 server 生命週期都共享(不是 per-session)
每次進到首頁 (/) 會把 ip 重設為 2,並把 script[4] 強制重設為 “USER_INPUT”
所以我嘗試使用JUMP 0來輸入 答案就顯示了
OBF

給了一個py加密程式還有txt檔
1 | 3e08772c224960093145070318575a0e741e050c7a2d745a1b6f5a0d5834322b |
應為被加密的密文(好奇怪為甚麼不是Crypto題反正我Crypto一題都沒解出來反正我是不懂admin的sha1為甚麼會等於61cfc9d3dadc5504391b872d170bbe73f6ca0d77
說回正題腳本是用狀態機(以字串位址為 key 的函式表)分段填滿 memory[0..63],組出一把 64 bytes 的 _key,再拿 _key 對 flag 做 XOR 加密,最後把結果輸出成 hex
因此,只要依照程式邏輯還原 _key,再用 output.txt 反 XOR,就能得到 flag
解密後得FhCTF{08fu5c471n6_Py7h0n_15_fun}
The Lock

給一個exe執行檔
在ida中逆向
找到有趣的地方
check_password 會先以 substr(6, len-7) 取出 {} 中的字串 inner,並限制其長度為 26。
接著程式宣告兩個常數陣列:key[4] 與 expected[26]。
依規則寫腳本
1 | key = [85, 51, 102, 17] |
取得flag FhCTF{R3v3rs3_Eng1n33r1ng_1s_Ar7}
壞掉的解碼器

其中一個txt記錄著密文
1 | 2781ACE7A1534E1231F7B84AD05565FEFB484A86E6ECD5C76686276A57658F79686098C6A5F0593D395543ABFF118410B2F02CF61FA5 |
試著運行decrypt
好吧 真的是壞掉的解碼器
它讀取密文時只讀了前面一小段
逆向可得他解密的方式為
右旋 3 bits
1 | b_rot = ROR(b, 3) |
更新 PRNG(線性同餘)
1 | seed = (seed * 0x41C64E6D + 0x3039) & 0x7fffffff |
取 keystream
1 | k = seed % 255 |
XOR 得到明文
1 | plain = b_rot ^ k |
再把原始密文加回 seed
1 | seed += b |
即可得flag FhCTF{Why_not_use_std::string_instead_of_char_arrays?}
OSINT
接下來就是通靈題啦
Trace the Landmark


題目給到的是羅馬萬神殿
這題屬於是格式寫起來很麻煩了
取得flagFhCTF{Piazza_della_Rotonda_00186_Roma_RM_Italy}不得不說每題格式講的有夠不清楚
島 1

這題我看到是直接傻眼的
餐廳的字也被馬掉只能隱約看到新 口餐廳
超鳥誰知道
但開google以圖搜圖還真搜到了
(寫到這的時候還沒吃東西看的我好餓
特色菜應該就是在地美食了
一個一個爆破
The FH Gift

郵件為multipart/mixed附base64值
將base64 解碼得到PK開頭
判定為 ZIP(檔頭 PK\x03\x04)
解壓縮得到 flag.txt
讀出後得到flag FhCTF{M1M3_Typ3s_C4n_B3_D3c3pt1v3}
沒戴安全帽的騎士

通靈題直接問AI
flag FhCTF{2014_Kymco_Many50}
EXIF的「拍攝座標」
這題我真的要罵人了
題目裡面明明講了EXIF
然後拿exiftool掃了之後只發現一個緯度
我便以為是要找到那個噴水池的經度
(甚麼鬼題目為啥不用map定位
我們一群人就在群組裡面吵這題
然後搞笑的來了
哈 不是戈門
8個多小時了(還在用AI驗題啊
然後有趣的是它修好的檔名就是經度
趁大家在吵的時候也是搶到了首殺
flag FhCTF{2511757831215189068}
Lithium exploration

不得不說他對格式的描述真夠爛的
查了一下wiki
南美的被譽為天空之境的地方為玻利維亞(Bolivia)的尤尼斯鹽沼(Salar de Uyuni)
盛產礦物被譽為站在黃金上的乞丐
然後那個地方盛產鋰礦的原始形式為液態的鹽滷水(Brine)
那上繳的形式為什麼呢礦物(Mineral)
所以是Dissolved Lithium(含鋰離子的鹽水)還是Lithium Brine(富鋰鹵水)又或是Lithium Carbonate(加工後為碳酸鋰)
都不是答案是Lithium(鋰)
。。。。。
不是這說法不是元素(Element)嗎
flag FhCTF{Bolivia_SalardeUyuni_Lithium}
SRL

題目給了一圖
看得出來這裡與大巨蛋很近
從101的角度判斷這裡就是松煙誠品
漂亮的圓頂 1

猜猜這是哪
從以圖搜圖的方式來說肯定是多爾瑪巴赫切清真寺
當然正確答案也是多爾瑪巴赫切清真寺
但似乎出題的人不認為是多爾瑪巴赫切清真寺
他出了個與他相甚遠的索菲亞大教堂(我第一次問AI也在那附近還想說怎麼可能
(然後晚上又被改回來了?
伊斯坦堡 多爾瑪巴赫切清真寺
島2

wiki都有寫
在金門的建功嶼
漂亮的圓頂 2
原本使用照片去搜時,只找到博斯普魯斯海峽附近相關的公共渡輪之類的,但都要錢。在快放棄這題時忽然想到,不然把範圍拉大,以國家來查,然後加上優先查看旅遊達人的介紹(畢竟都說是免費了 他們一定最喜歡這種東西) 然後就在查到第2 3 個就看到一個黑黑的人的影片就是這部https://www.youtube.com/watch?v=0cRrqQ8zIuY 裡面的資料在搜尋一下就有答案了
Art Work
這個題目提供了一張圖,我先丟到google來做以圖搜圖,這時取得了一個資訊,展覽名稱是屏東落山風藝術季,但將2025年12月20日–2026年3月1日 的資料當flag失敗,才發現這圖片只出現在某年的展覽,這時我查看多個以圖搜圖的結果,最後從https://500times.udn.com/wtimes/story/12672/6734221 取得flag的相關資訊
Blue Team
User’s Bad Day
這是一道典型的網路封包分析 (Network Traffic Analysis)
根據提供的incident_log.pcap內容片段
可以分析出 SMB 協定與 NTLM 驗證的相關資訊
使用者輸入的主機名稱 (Hostname)
在封包內容中,我們可以觀察到名稱解析與連線的請求
看到類似 Name Query: fulesrv 的資訊
這就是使用者嘗試查詢的主機名稱
攻擊者攔截到的帳號名稱 (Account Name)
在 SMB 協商過程中,會包含 NTLM 驗證封包 (NTLMSSP)
分析證據:在 中,可以看到 NTLMSSP 的內容,其中包含以寬字元(中間有空格)顯示的字串:D O M A I N B o b W O R K S T。
這是 NTLM 驗證結構,其中 D O M A I N 代表網域,而B o b代表使用者帳號名稱。
Bob
操作哪個檔案 (File Name)
在建立連線後,使用者嘗試開啟或操作某個檔案。
接續在 SMB 標頭之後,出現了被分段的字串 t e 和 s t
SMB 傳輸檔案名稱時通常使用 Unicode (寬字元),這裡拼起來就是 test
(題目要求不含副檔名,且封包中未見明顯副檔名痕跡
test
取得flag FhCTF{fulesrv_Bob_test}