How ToSoftware

[TR] PHP Type Juggling Zafiyeti

[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:

1-variable-declaration
variable-declaration

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:

ageInteger-ageString
ageInteger-ageString
sonuç-ageInteger-ageString
sonuç-ageInteger-ageString

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.

var_dump-fonksiyonu
var_dump-fonksiyonu
var_dump-fonksiyonu-sonuç
var_dump-fonksiyonu-sonuç

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:

all-loosely-comprassions-with-==
all-loosely-comp-with-==

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:

üç-eşittir-işareti
üç-eşittir-işareti
üç-eşittir-işareti-sonuç
üç-eşittir-işareti-sonuç

Aşağıda üç eşittir işareti kullanılarak yapılan karşılaştırmaların sonuçlarını inceleyebilirsiniz:

strict-comparisons-with-===
strict-comparisons-with-===

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:

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:

intigriti-challenge
intigriti-challenge

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:

0e-ile-başlayan-hash
0e-ile-başlayan-hash
0e-ile-başlayan-hash-sonuç
0e-ile-başlayan-hash-sonuç

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:

md5-kontrol
md5-kontrol
md5-kontrol-sonuç
md5-kontrol-sonuç

Ş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:

admin-paneline-giriş
admin-paneline-giris

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:

zafiyet-çözüm
zafiyet-çözüm
zafiyet-çözüm-sonuç
zafiyet-çözüm-sonuç

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:

İlgili Makaleler

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Başa dön tuşu