CTF01 Çözümü

Merhaba

CTF’ler, katılımcıları farklı düşünmeye zorlamalı ve yarışmacılar için öğreti olmalıdır. Bu nedenle şimdiye kadar özellikle XSS olmak üzere bir çok ufak yarışma düzenlemiş bulunmaktayım. Bu yarışmalardan en sonuncusu ise geçtiğimiz gün yayınladığım CTF01 numaralı yarışma. 

Toplamda 491 kişinin katıldığı, yada bir diğer söyleyiş ile 491 farklı IP adresinden trafiğin oluştuğu bu yarışma sadece 2 kişi tarafından çözüldü.

Tebrikler

Twitter üzerinden gönderilen bildirimlere göre Oğuz DOKUMACI ve Cihad ÖĞE bu ufak level’i geçmeyi başardılar. Kendilerine teşekkür ederim.

Çözüm

CTF01 level’i http://ctf01.mdisec.com adresinde online durumdaydı ve site ziyaret edildiğinde karşımıza aşağıdaki PHP kodları çıkmaktaydı.

Kaynak koda bakarak aşağıdaki sözde kodun olduğu anlaşılmaktadır.

  1. Oturum başlat
  2. Eğer session listesinde secret isimli değişken yok ise private_key_builder() secret oluştur.
  3. Eğer talep POST talebi değilse veya secret adından HTTP POST değişkeni yoksa kaynak kodu göster ve son bul.
  4. Eğer session’da secret değişkeni varsa ve HTTP POST talebinde de secret isminde değişken geldiyse, bunları karşılaştır.
  5. Sonuç TRUE ise flag.php dosyasında ki get_flag() fonksiyonu çağırılacaktır.
  6. Eğer False ise ekrana mevcut secret’i yaz.
  7. Kullanıcıdan gelen secret tahminini htmlspecialchars() fonksiyonundan geçir.
  8. Yeni bir secret key oluştur.

Buradan anlaşılan şu ki, eğer HTTP POST talebinde göndereceğimiz secret key tahminimiz doğru değilse, ekrana bir takım mesajlar kullanıcıya gösterilip ardından yeni bir secret key üretilecektir.

Bu kısma kadar bir çok kişinin oyunu doğru yorumladığını düşünüyorum. Asıl önemli kısım ise buradan sonra başlamakta, private_key_builder() fonksiyonunun ürettiği key’i tahmin etmeli mi ? Edeceksek nasıl yapabiliriz ?

Key oluşturulurken kullanılan veri kaynaklarının yarısı saldırgan tarafından bilinen kaynaklardır. User-Agent ve REMOTE_ADDR olarak adlandırılan bu iki veri, doğrudan saldırgan tarafından bilinebilir.

Time() fonksiyonu sunucunun mevcut zamanının unix timestamp’ini döndürmektedir. Bu durumda sunucununu zaman bilgisine ihtiyacımız vardır. Bu veriye ise HTTP Response Header’da yer alan Date: bilgisinden elde edilebilmektedir. Geriye bir tek mt_rand() fonksiyonu kalıyor ki onuda tahmin etmek veya bilmek gerçekten zor.

Bu şartlar altında Brute-force’un teoride evet ama pratikte son derece zor olacağı düşünülmelidir. Bu nedenle başka bir yol daha olmalı ? Sorusu akıllara gelir.

Wut, Wait a minute ?!

Ya mt_rand() değerini doğru tahmin ederek, talebi sunucuya tam olara belirlediğimiz zaman saniyesinde ileteceğiz, yada yeni secret key oluşmadan eskisini elde etmenin bir yolunu bulacağız.

Son 4 satır koda baktığımızda dikkatimizi çeken şey htmlspecialchars() fonksiyonunun kullanılmış olmasıdır. Ayrıca bu fonksiyon parametre olarak bizim tahmin secret’imizi almaktadır ve en en önemli nokta ise, htmlspecialchars() çağırılmadan önce tahmin etmek için can attığımız mevcut secret değeri ekrana yazdırılmaktadır.

http://stackoverflow.com/questions/16384515/why-is-htmlspecialchars-so-slow

Sevgili  PHP geliştiricilerimizin burada tartıştığı üzere htmlspecialchars() son derece yavaş bir fonksiyondur.

Yani biz htmlspecialchars() fonksiyonunun yavaş olmasından yararlanarak, yeni secret oluşturulmadan hemen önce mevcut secret’i elde edip, hemen yeni bir HTTP talebi oluşturarak tahmini doğru bir şekilde gerçekleştirebiliriz!

Exploit

Yukarıda anlatılanları gerçekleştiren ufak bir python betiği aşağıdaki şekilde yazılabilir. Basit olarak 50000 adet < işareti tahmin olarak gönderilerek htmlspecialchars() fonksiyonunun 10-20 saniye boyunca asılı kalması sağlanarak exploitation gerçekleştirilebilir.

Sonuç ise;

  • Oğuz DOKUMACI

    Bu güzel CTF için bir kez daha Teşekkürler

  • ctf

    bence videolu anlatımlarınız daha iyi olacaktır. Ctf için teşekkürler