Crypto Failures

Crypto Failures Challenge

author image

Written by

Furkan İbiş

Published on

Oct 19 26

Crypto Failures

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

  1. What is the value of the web flag?
  2. 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

  1. Hedef sistemde riskli bir yorum satırıyla karşılaştım: “TODO remember to remove .bak files.” Bu satır, sistemde .bak uzantılı yedek dosyaların bulunduğunu ve olası bilgi sızıntısı riski oluşturabileceğini gösteriyor.

  1. Web hizmetine bağlanır bağlanmaz secure_cookie ve user adı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 100

Düşü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:

  1. Hedef sistem, çerezlerden secure_cookie ve user çerezlerinin tanımlı olup olmadığını kontrol ediyor. Eğer tanımlıysa verify_cookie fonksiyonuna ENC_SECRET_KEY gönderip doğrulama yapıyor.

  1. verify_cookie fonksiyonu, secure_cookie ve user değerlerini alıyor. Fonksiyon önce $user, $HTTP_USER_AGENT ve $ENC_SECRET_KEY değerlerini birleştirerek bir string oluşturuyor: string = $user + $HTTP_USER_AGENT + $ENC_SECRET_KEY. Ardından secure_cookie değerinin ilk iki karakterinden bir salt üretiyor ve make_secure_cookie(string, salt) çağrısı ile elde edilen değeri crypted_cookie ile karşılaştırıyor — eşleşiyorsa doğrulama başarılı sayılıyor.

  1. verify_cookie fonksiyonunun sonucu true dönerse sistem doğrudan user ç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ıkla user ile secure_cookie değerlerinin uyumsuzluğundan — "you are not logged in" yanıtı geliyor.

  1. make_secure_cookie fonksiyonunu incelediğimde, gönderdiğimiz string değerini 8 karakterlik parçalara böldüğünü ve bu parçaları cryptstring fonksiyonu aracılığıyla şifreleyip secure_cookie değerine yazdığını gördüm. Bu kısım, ileride anlatacağım aşamada kritik bir rol oynayacak.

  1. Son olarak, eğer user ve secure_cookie değerleri tanımlı değilse — yani kullanıcı sisteme ilk kez giriş yapıyorsa — generate_cookie fonksiyonu devreye giriyor ve bu çerezleri oluşturup kullanıcıya atıyor. generate_cookie fonksiyonu da aynı mantıkla çalışıyor: user, HTTP_USER_AGENT ve ENC_SECRET_KEY değerlerini birleştirerek bir secure_cookie_string oluşturuyor. Ardından bu string’i, daha önce bahsettiğim make_secure_cookie fonksiyonu aracılığıyla şifreleyip secure_cookie değeri olarak atıyor. SALT üretimi için ise tamamen rastgele, 0–9, a–z ve A–Z karakterlerin 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!