L3akCTF 2026 — catvault part 1 Çözümü
Yalnızca kendi sırlarını okuttuğunu iddia eden bir kasa; ta ki settings uç noktası oturumdaki user_id'yi ezmeye ve vault sorgusu yanlış kolona göre filtrelemeye izin verene kadar.
L3akCTF 2026 — catvault (bölüm 1)
Sırlarını sakladığın bir "kedi kasası"; sayfa da sana "kimsenin senin kasandaki sırları görmesi impawssible" diye söz veriyor. İki küçük kusur bu sözü yalana çeviriyor: kendi oturumundaki user_id'yi ezmene izin veren bir settings uç noktası ve yanlış kolona göre filtreleyen bir vault sorgusu. İkisi birlikte, bayrağı taşıyan admin kaydını okumamızı sağlıyor.
Kapsam: yalnızca yerel reprodüksiyon. Challenge kaynağı CTF arşivinde açık; hiçbir dış sisteme dokunulmadı.
1. Kaynağı okumak
Flask uygulaması, MariaDB arka ucu, sha256 parola özeti. Bariz enjeksiyon noktaları temiz — login ve register parametrik sorgu kullanıyor:
# db.py
def login(username, password):
cursor.execute("SELECT id, name, password FROM users WHERE name = ? AND password = ?;",
(username, sha256(password).digest().hex()))Oturum çerezi her açılışta rastgele üretilen bir anahtarla imzalanıyor (app.secret_key = random.randbytes(32)), yani çerez uydurmak söz konusu değil. Ama iki yer o kadar dikkatli değil.
Vault okuma sorgusunu f-string ile kuruyor ve yanlış kolona göre filtreliyor:
# db.py
def get_vault_entries(user_id):
cursor.execute(f"SELECT id, content FROM vault WHERE id = {user_id};") # `user_id` değil, `id`
return [i for i in cursor]Settings uç noktası ise gelen her anahtarı oturuma yazıyor; yalnız alt çizgi ile başlayan anahtarları ve string olmayan değerleri reddediyor:
# app.py — POST /api/settings
for key, value in incoming.items():
if not isinstance(key, str) or key.startswith("_") or not isinstance(value, str):
continue
session[key] = value # `user_id` düz bir string -> izinliuser_id alt çizgi ile başlamıyor ve bir string, dolayısıyla doğrudan oturuma giriyor.
2. Zincir
Admin kurulumu bayrağı admin'in vault içeriği olarak ekliyor ve bu satır tablodaki ilk satır oluyor:
# db.py — create_admin()
cursor.execute("INSERT INTO users (name, password) VALUES ('admin', 'nologin');")
cursor.execute(f"INSERT INTO vault (user_id, content) VALUES ({cursor.lastrowid}, '{FLAG}');")Yani admin'in vault satırı id = 1. Plan:
- Herhangi bir kullanıcı kaydet.
POST /api/settingsile{"user_id": "1"}gönder — oturumdakiuser_idezilir.GET /vault—get_vault_entries("1")şunu çalıştırır:SELECT id, content FROM vault WHERE id = 1, ve admin'in satırını döndürür.
Sorgu user_id yerine vault birincil anahtarı id üzerinden filtrelediği için user_id'yi 1'e yöneltmek admin'in sırrını okumaya yetiyor. Değer ayrıca string olarak araya sokulduğu için zincirleme sorgu da mümkün — ama en kısa yol yanlış-kolon okuması.
3. Reprodüksiyon
Ortamda MariaDB sunucusu olmadığı için gerçek Flask uygulamasını app.test_client() ve db.py yerine geçen, SQL metnini birebir koruyan (hatalı f-string dahil) bir sqlite3 taklidiyle sürdüm. Bayrak, orijinaldeki gibi FLAG ortam değişkeninden geldi:
register -> 302 /vault
vault(before) contains flag: False
POST /api/settings -> 200 {'ok': True, 'saved': {'theme': 'dark', 'user_id': '1'}}
vault(after) flag: L3AK{l0c4l_r3pr0_fl4g_n0t_th3_r34l_0n3}Sunucunun ikinci vault yüklemesinde gerçekte çalıştırdığı ifade ve döndürdüğü satır:
SQL: SELECT id, content FROM vault WHERE id = 1;
rows: [(1, 'L3AK{l0c4l_r3pr0_fl4g_n0t_th3_r34l_0n3}')]Not: arşivdeki flag.txt içeriği bilinçli bir tuzak (...to_waste_your_time_if_youre_using_an_llm); bölüm 1 için gerçek bayrak admin'in vault kaydıdır (FLAG ortam değişkeni), o dosya değil.
4. Bayrak ve dersler
Yerel taklit bayrak, gerçeğin okunduğu yöntemin aynısıyla okundu:
L3AK{l0c4l_r3pr0_fl4g_n0t_th3_r34l_0n3}Dersler:
- İstek verisinin ayrılmış oturum anahtarlarını yazmasına izin verme. Alt çizgi engeli yerine, yazılabilir anahtarların izin listesi doğru yaklaşımdır.
- Sorgu, sahipliği taşıyan kolona göre filtrelemelidir (
user_id), asla global otomatik artanid'ye göre değil. Buradaki tek kelimelik hata bir yetki atlatmasıdır. - Değerler "oturumdan geliyor" diye yine de parametrelendirilmeli — o oturum alanı bir adım önce saldırganın kontrolündeydi.
Ne düşünüyorsun?
Tepki bırakarak geri bildirim ver