Konudaki Başlıklar
[TR] PHP Type Juggling Zafiyeti
Merhaba arkadaşlar, bu yazımızda PHP’de bulunan “Type Juggling” adlı zafiyeti inceleyeceğiz. Ardından Intigriti Bug Bounty platformunun yayınladığı, yine bu zafiyetle ilgili olan bir challenge ile bilgimizi pekiştireceğiz. Son olarak zafiyetin nasıl giderilebileceğine bakacağız.
Zafiyet PHP’de veri tipleri karşılaştırılırken katı kurallar olmamasından kaynaklanıyor ve authentication bypass gibi büyük etkilere neden olabiliyor. Vakit kaybetmeden zafiyeti temelden incelemeye başlayalım.
Katı Kuralları Olmayan Dil Bir Dil
PHP’de bir değişken oluştururken diğer dillerde olduğu gibi verinin tipini belirtmemize gerek yoktur. Bir değişken oluşturmak için basitçe bir dolar ($) işareti koymamız yeterli olur:
Buna ek olarak PHP verileri birbiriyle karşılaştırırken farklı veri türlerini karşılaştırabilmek için veri tipi dönüşümü yapar. Bu sebeple PHP “loosely typed language” yani katı kuralları olmayan bir dil olarak bilinir. Mesela veri tipi integer olan 16 ile veri tipi string olan “16”yı karşılaştırdığımızda PHP bu iki değeri denk olarak algılar:
NOT: var_dump() fonksiyonu içindeki parametrenin veri tipini ve parametrenin değerini verir. Bu örnekte fonksiyonun içinde $ageInteger değişkeni ile $ageString değişkeni karşılaştırılıyor; eğer değerler birbirlerine denkse fonksiyon True, değilse False return ediyor. Yani karşılaştırmanın sonucu boolean veri tipi oluyor ve veriler denk olduğu için sonuç True oluyor.
Eğer var_dump fonksiyonu aşağıdaki gibi kullanılsaydı değişken integer olduğu ve değeri 123 olduğu için sonuç int(123) olurdu.
Yani özet olarak PHP’de veriler ve veri karşılaştırma kuralları diğer dillere kıyasla daha gevşek tutuluyor. “16” == 16 sadece bu tarz veri tipi dönüşümlerinden biriydi, aşağıdaki tabloda farklı veri tiplerinin karşılaştırıldığında hangi sonucu vereceğini görebilirsiniz:
Veri Dönüşümü Nasıl Engellenir?
Peki biz bu veri dönüşümlerini nasıl engelleyebiliriz? Fark ettiyseniz az önce yaptığımız karşılaştırmalarda “==” işaretini kullanıyorduk. Asıl veri dönüşümüne sebep olan operatör bu iki eşittir işaretiydi. İki eşittir işareti, eşitlik yerine denkliğe bakar. Yani iki değer birbirinin tıpatıp aynısı olmasa bile aynı şeyi ifade edebiliyorsa bu iki değer denktir. Eğer biz karşılaştırma yaparken denklik yerine eşitliği ölçmek istiyorsak üç eşittir işareti “===” kullanmalıyız. Üç eşittir işaretini kullandığımız vakit “12” === 12; karşılaştırması artık False değerini return eder:
Aşağıda üç eşittir işareti kullanılarak yapılan karşılaştırmaların sonuçlarını inceleyebilirsiniz:
Buna ek olarak PHP 7 ile veri türlerine daha katı davranmamızı sağlayacak bazı içerikler geldi. İçerikleri bu yazıda ayrıntılı incelemeyeceğim fakat siz aşağıdaki linkten bahsettiğim içeriklere göz atabilirsiniz:
- https://www.w3schools.com/php/php_functions.asp (PHP is a Loosely Typed Language ve PHP Return Type Declarations başlıkları)
Zafiyet
Peki eğer bir PHP developer bir login formu oluştururken üç eşittir işareti yerine iki eşittir işareti kullanırsa ne gibi bir zafiyet ortaya çıkar? Intigriti’nin yayınladığı challenge üzerinden bunu inceleyelim:
Bu PHP kodunda; bir kullanıcı “admin” kullanıcı adı ile login olmaya çalışırsa, kod kullanıcının girdiği şifrenin MD5 hash’ini alıp gerçek şifrenin hash’i ile karşılaştırıyor. Eğer hash’ler eşleşirse kullanıcı panele admin olarak giriş yapıyor.
Yukarıdaki örnekte bulunan gerçek şifrenin hash’i 0e… ile başlayan yani bilimsel e notasyonunu kullanan bir değerdir. Bilimsel e notasyonu çok uzun sayıları kısa biçimde yazmak için kullanılır. Mesela 1.000.000 sayısı ‘1e6’ olarak yazılır. MD5 gibi hash fonksiyonları 0-9 sayılarından ve a-f harflerinden oluşan onaltılık kodla kullanır. Bu sakıncalıdır, çünkü eğer yeterli sayıda denenirse 0e… ile başlayan bir hash bulunabilir.
Peki 0e… ile başlayan bir hash bulursak ne olur? Bunu bir örnekle göstermeye çalışayım:
Evet… PHP, 0e ile başlayan iki hash’i denk olarak görüyor ve karşılaştırmanın sonucu True return ediyor. Peki bunun etkisi nedir? Eğer bir database’de birden fazla kullanıcının hash’i bu tarzda ise bir şifre ile birden fazla hesaba login olunabilir. İlginç, değil mi?
Yukarıdaki challenge’a dönersek login mekanizmasını bypass etmemiz için yapmamız gereken şey 0e ile başlayan bir MD5 hash’i bulmak! Aşağıda kaynaklar kısmına bırakacağım bir yazıda “240610708” ve “QNKCDZO” değerlerinin 0e ile başladığını okudum. Basitçe kontrol edebiliriz:
Şimdi bu iki değerden birini şifre olarak girersek karşılaştırmanın sonucu True olacağı için panele admin olarak giriş yapmış olacağız. Hemen deneyelim:
Gördüğünüz üzere PHP’de bulunan veri tipi dönüşümlerinden ortaya çıkan zafiyeti kullanarak bir yetkilendirme sistemini bypass ettik. Peki bu zafiyeti nasıl giderebiliriz? Çözüm basit, kodumuzda bulunan kullanıcı şifre input’u ile gerçek hash’in karşılaştırılması yapılırken kullanılan iki eşittir operatörü yerine üç eşittir operatörünü kullanmak:
SON
Evet arkadaşlar, yazının sonuna geldik. Umarım faydalı bir içerik olmuştur. Gelecek yazılarda görüşmek üzere…
KAYNAKLAR:
- https://www.netsparker.com/blog/web-security/php-type-juggling-vulnerabilities/
- https://securitygoat.medium.com/intigritis-php-challenge-breakdown-178f5d003986
- https://twitter.com/intigriti/status/1424334586104926215/photo/1