Avatar Ben Süleyman ERGEN. Siber güvenlik ile uğraşmayı seviyorum. Bol bol ctf çözer ve write-up yazarım. Burada ise edindiğim tecrübeleri ve bilgileri paylaşıyorum.

Php Input Validation

PHP INPUT VALIDATION

Elbette kullanıcıdan gelen veriyi doğrulamak (validate) dinamik web uygulamaları için bir oldukça önemlidir. Geçerli olmayan kullanıcı inputları hatalara neden olabilir. Bu yüzden input validation bir zorunluluktur. PHP input validation için çeşitli fonksiyonları built-in olarak bize sağlar.

Bu yazıda 4 konuyu işleyeceğiz

  • Request metodunu doğrulama
  • Kullancı inputunu kontrol etme
  • İnputu doğrulama (validate)
  • İnput validation için kendı sınıfını oluşturma

Request Metodunu Doğrulama

Html formunu oluşturmadan önce formda kullanmak istediğiniz http metoduna karar vermelisiniz. Birçok http metodu olmasına rağmen genellikle GET ve POST metodları formlar için daha sık kullanılır. Sonuç olarak GET mi POST mu ???

GET Metodu

  • Kaynaktan bir şey talep etmek için kullanılır.
  • Anahtar değer şeklinde verileri URL içinde gönderir.
  • Kaynaktan veri almak için kullanılır.
  • Hassas verileri göndermek için kullanılmamalıdır.
  • POST metoduna göre daha az güvenlidir.
  • Farklı veriler farklı URL oluşturur
  • Tarayıcı tarafından cache edilebilir.
  • Tarayıcı geçmişinde kalır.
  • Uzunluk limiti vardır.
  • Sadece ASCII veri gönderilerbilir

POSR Metodu

  • Server’a veri göndermek için kullanılır.
  • Hassas verileri göndermek için kullanılır.
  • GET metoduna göre daha yavaştır.
  • Veriyi http requestinin body kısmında gönderir.
  • Tarayıcı geçmişinde kalmaz.
  • Uzunluk/boyut kısıtlaması yoktur.
  • Herhangi bir veri gönderilebilir.

Kredi kartı, parola gibi hassas bilgiler gönderiyorsanız metodunuz POST olmalı. Eğer serverdan bir veri alıyorsanız kullandığınız metod GET olmalı.

Birtane html formu oluşturalım. Ben POST metodunu seçtim ve action kısmına action.php scriptini verdim.

<form method="POST" action="action.php">

    <!-- form elemanları buraya -->

</form>

Tarayıcı formu submit edince form datasını action.php sayfasına gönderecek. Ben action.php scriptini şu şekilde oluşturdum. Önce http metodunu kontrol ediyor:

<?php

if ($_SERVER['REQUEST_METHOD'] === 'POST')
{
    // request metodu geçerli
}
else
{
	exit('Request metodu geçersiz!!!');
}

Yada daha kısa olarak şu şekilde:

<?php

if ($_SERVER['REQUEST_METHOD'] !== 'POST')
{
    exit('Request metodu geçersiz!!!');
}

if kontrolü içinde istediğiniz http metodunu kontrol edebilirsiniz. Bu örnekte action.php sadece POST metodunu kontrol edecek. Eğer metod POST değilse bir hata gösterecek. Http metod kontrolü basit bir şekilde böyle yapılabilir.

Kullanıcı Verisini Kontrol Etme

Bir hata olmaması için kullanıcı verisini kontrol etmeliyiz. Bu hatanın nasıl oluştuğuna bakalım. Bir php kodunuz var POST metoduyla gelen değeri local değişkene atıyor:

$email = $_POST['email'];

Eğer “email” post metodu ile set edilmediyse, php bir hata üretecek: Undefined Index: Email

Bu yüzden input değişkeninin olup olmadığını kontrol etmeliyiz.

Boolean Olmayan İnputlar

PHP deki empty() fonksiyonu bu durum için oldukça önemlidir. Bu fonksiyon argümanın boş olup olmadığını kontrol eder. Aşağıdaki durumlarda TRUE değerini döner:

  • "" boş string
  • 0 integer olarak 0
  • 0.0 float olarak
  • “0” string olarak
  • NULL
  • “FALSE`
  • array() boş bir array
if( ! empty($_POST['email']))
{
    $email = $_POST['email'];
}

Yukarıdaki örnekte $_POST["email"] boş değilse $email değişkeni tanımlanır ve değeri değişkene atanır. Bir önceki hata ile karşılaşmayız. Eğer email alanı zorunlu ise bir hata üretebiliriz. Buna daha sonra değineceğiz.

Boolean İnputlar

Boolean değişlenler için isset() fonksiyonu kullanılabilir. isset() fonksiyonu bir değişkenin tanımlandığını (declerated) ve değerinin NULL olmadığı durumlarda TRUE döner.

if (isset($_POST['boolean']))
{
    $boolean = $_POST['boolean'];
}

İnputları Doğrulama

XSS Saldırılarından Korunma

Öncelikle XSS saldırılarından korunmamız gerekiyor. Aşağıdaki forma kullanıcıların kullanıcı adlarını yazmaları için bir alan olduğunu düşünün.

<form method="POST" action="">

	<input type="text" name="username">
	<input type="submit" name="submit">

</form>

Bir saldırganın kullanıcı adı olarak şöyle bir js kodu girdiğini düşünün

<script>location.href='https://www.saldirgansite.com'</script> 

Ve sonra formu submit etti. Bizde bir validation uygulamadan veritabanına kaydettik. Daha sonra kullanıcıların kullanıdıadlarını listelediğimiz bir sayfa hazırladık. Birde saldırganın gönderdiği kullanıcı adını yazdırdığımızda, herkim sayfamızı zirayet ederse saldırganın sitesine bizi yönlendirecek. (https://www.saldirgansite.com). İşte basit tabiriyle XSS böyle bir şey.

Web uygulamamızı XSS saldırılarından korumak oldukça basit. htmlspecialchars() fonksiyonu html deki kaçış karakterlerini temizler. Saldırganın girdiği input:

<script>location.href='https://www.attacker.com'</script>

Şu hale dönüşür:

&lt;script&gt;location.href='https://www.attacker.com'&lt;/script&gt;

Bir örneğe bakalım.

if (!empty($_POST['username']) && !empty($_POST['email']))
{
	$username = htmlspecialchars($_POST['username']);
	$email = htmlspecialchars($_POST['email']);
}

Kullanım yukarıdaki şekilde. Yapmamız gereken bir şey daha var. Kullanıcı inputundan gelen fazla boşlukları silmeliyiz. Böylece veritabanımızda fazladan yer kaplamazlar. PHP deki trim() fonksiyonu burada kullanılabilir.

if ( ! empty($_POST['username']) && ! empty($_POST['email']))
{
	$username = trim(htmlspecialchars($_POST['username']));
	$email = trim(htmlspecialchars($_POST['email']));
}

Email URL Integer Doğrulama

PHP değişkenleri doğrulamak için filter_var() fonksiyonunu bize sunar. Fonksiyondaki 2. parametreyi çeşitli değerleri doğrulamak için kullanabiliriz. Bu fonksiyon bir hata olduğunda yada input geçersiz olduğunda FALSE döner.

Email Doğrulama

Basit bir şekilde email doğrulamak için filter_var() fonksiyonunun 2. parametresine FILTER_VALIDATE_EMAIL değerini verebiliriz. Eğer email geçerli değilse FALSE döner.

if ( ! empty($_POST['email']))
{

	$email = trim(htmlspecialchars($_POST['email']));
	$email = filter_var($email, FILTER_VALIDATE_EMAIL);

    if ($email === FALSE)
    {
		exit("Email Geçerli Değil !!!");
	}
}

URL Doğrulama

FILTER_VALIDATE_URL flagi ile URL doğrulamasını filter_var() fonksiyonu ile yapabiliriz. Eğer URL doğru formatta değilse FALSE döner, doğru formatta ise parametre olarak verdiğimiz URL döner.

if ( ! empty($_POST['url']))
{
	$url = trim(htmlspecialchars($_POST['url']));
	$url = filter_var($url, FILTER_VALIDATE_URL);

	if ($url === false) {
		exit('URL Geçerli Değil !!!');
	}
}

Integer Doğrulama

filter_var() fonksiyonu ile FILTER_VALIDATE_INT flagini kullanarak integer değerleri doğrulayabiliriz. Bu metodu kullanmamızın avantajı, string olarak gelen integer değerini integer sayıya dönüştürmesidir. Bizim tekrardan değeri integera dönüştürmemiz gerekmez.

if ( ! empty($_POST['number']))
{	
	$number = $_POST['number'];
	$number = filter_var($number, FILTER_VALIDATE_INT);

	if ($number === false) {
		exit('Integer Geçerli Değil !!!');
	}
}

Bu fonksiyone input gönderirken

  • 25 (integer) değişmez
  • “25” stringi integera dönüştürülür
  • 25.11 (float) FALSE döner. Çünkü integer değil
  • TRUE (boolean) 1 döner(integer)
  • FALSE (boolean) FALSE döner (boolean)
  • arrayler, nesneler, stringler(numerik karakter içermeyen) FALSE döner

Not: bütün FALSE değerleri integer olmadığı anlamına gelir.

Boolean Doğrulama

filter_var() fonksiyonu ve FILTER_VALIDATE_BOOLEAN flagi ile boolean değerler doğrulanabilir. Bu fonksiyon “on” “yes” “true” string değerlerinde (büyük küçük harf duyarlı değil) TRUE değerini döndürür. Diğer her şeyde FALSE döner.

Çoğu tarayıcı checkbox işaretlendiğinde “on” stringini gönderir. Bir örnekle görelim:

if (!empty($_POST['check']))
{
	$check = $_POST['check'];
	$check = filter_var($check, FILTER_VALIDATE_BOOLEAN);	
}

fileter_var() fonksiyonuna input değerini FILTER_VALIDATE_BOOLEAN flagi ile gönderdiğimizde “on” string değeri TRUE değerine dönüştürülecek. Bu sayede inputa boolean olarak davranabiliriz.

Biz bazı fonksiyonları şimdiye kadar gördük. Fakat hepsini burada anlaymaya çalışmak iyi bir pratik değil. Bunun için PHP nin dökümantasyonunu inceleyebilirsiniz. php data filtering Şimdi input validation için bir sınıf oluşturalım.

Input Doğrulama İçin Kendi Sınıfını Oluştur

Nesne yönelimli programlama ile ilgili bilginizin olduğunu düşünüyorum. Eğer yoksa internetten biraz araştırma yapabilirsiniz.

Sınıfımız XSS ve input doğrulama ile ilgilenecek. Sınıfın ismini Input verdim.

class Input {

}

Fonksiyonları statik olarak tanımladım. Bu sayede fonksiyonları daha kolay çağırabiliriz. Sınıfımız şu fonksiyonları barındıracak:

  • check() inputun boş oluş olmadığını kontrol edecek
  • int() integer değerleri doğrulayacak
  • str() kaçış karakterleriyle ve boşluklarla ilgilenecek
  • bool() inputu boolean değere dönüştürecek
  • email() email değeri doğrulayacak
  • url() URL leri doğrulayacak

Tüm sınıfın kodu yaklaşık olarak şu şekilde:

class  Input {
	static $errors = true;

	static function check($arr, $on = false)
	{
		if ($on === false)
		{
			$on = $_REQUEST;
		}
		foreach ($arr as $value)
		{	
			if (empty($on[$value]))
			{
				self::throwError('Veri Yok', 900);
			}
		}
	}

	static function int($val)
	{
		$val = filter_var($val, FILTER_VALIDATE_INT);
		if ($val === false)
		{
			self::throwError('Geçerli Bir Integer Değil', 901);
		}
		return $val;
	}

	static function str($val)
	{
		if (!is_string($val))
		{
			self::throwError('Geçerli Bir String Değil', 902);
		}
		$val = trim(htmlspecialchars($val));
		return $val;
	}

	static function bool($val)
	{
		$val = filter_var($val, FILTER_VALIDATE_BOOLEAN);
		return $val;
	}

	static function email($val)
	{
		$val = filter_var($val, FILTER_VALIDATE_EMAIL);
		if ($val === false)
		{
			self::throwError('Geçerli Bir Email Değil', 903);
		}
		return $val;
	}

	static function url($val)
	{
		$val = filter_var($val, FILTER_VALIDATE_URL);
		if ($val === false)
		{
			self::throwError('Geçerli Bir URL Değil', 904);
		}
		return $val;
	}

	static function throwError($error = "Bir Hata Oluştu", $errorCode = 0)
	{
		if (self::$errors === true)
		{
			throw new Exception($error, $errorCode);
		}
	}
}

Bu sınıfı bir dosyaya kaydedip kendi scriptiniz içinde include ederek kullanabilirsiniz. Bu sınıfın kullanımıyla ilgili bir kaç örnek görelim.

Hatalarla İlgilenme

Yukarıdaki kodda throwError isminde hata üreten bir fonksiyon oluşturdum. Eğer $error değişkeni TRUE ise hata üretecek. Eğer hata oluşturmak istemiyorsanız değeri FALSE olarak ayarlayın.

Input::$error = false;

Inputları Kontrol Etme

Inputları kontrol etmek için check fonksiyonunu oluşturdum. Bu fonksiyonda 2 argüman var. 1. argüman kontrol edilecek elemanlar listeri(array). 2. argüman ise içinde arama yapılacak “süper global array”. POST metodu için $_POST, GET metodu için $_GET, detault $_REQUEST.

Input::check(['email', 'password'], $_POST);

Bu kodla “email” ve “password” $_POST arrayi içinde bulunduğunu ve boş olmadığını kontrol edecek. Aksi durumda hata üretecek.

Doğrulama

Diğer fonksiyonları inputları doğrulamak için kullanabilirsiniz.

// integer doğrula
$number = Input::int($_POST['number']);

// string doğrula
$name = Input::str($_POST['name']);

// boolean'a dönüştür
$bool = Input::bool($_POST['boolean']);

// email doğrula
$email = Input::email($_POST['email']);

// URL doğrula
$url = Input::url($_POST['url']);

isset ve empty üzerine düşüncelerim

empty fonksiyonu bir değişkenin boş olup olmadığını kontrol eder. Eğer değişken tanımlanmadıysa true döner.

php > var_dump( empty($hello) );
bool(true)

isset ise bir değişken tanımlandıysa ve NULL değilse TRUE döner.

php > var_dump( isset($hello) ); # $hello tanımlı değil
bool(false)
php > $hello = NULL;
php > var_dump( isset($hello) ); # hello tanımlı değeri NULL
bool(false)
php > $hello = "";
php > var_dump( isset($hello) ); # tanımlı değeri boş string
bool(true)

empty fonksiyonunun şu özelliğinden dolayı kullanmayı pek tercih etmiyorum

php > var_dump( empty("0") );
bool(true)

"0" değerinde TRUE dönüyor. Ama string boş değil. Bu stringi sayı olarakmı değerlendiriyor bilmiyorum fakat inputtan string olarak 0 gelebileceği için isset fonksiyonunu kullanıyorum. Bu sayede değişkenin tanımlandığını anlıyorum ve ardından diğer doğrulama (validation) işlemlerini yapıyorum. Bu tamamiyle kişisel bir tercih. Bu konu ile ilgili sizin düşünceniz nedir? twitter yada mail olarak bana bildirebilirsiniz.

Input Validation ve Output Encoding

Genellikle input doğrulaması sırasında htmlspecialchars() fonksiyonunu kullandık. Bu fonksiyon html karakterlerini escape etti ve ondan sonra veritabanına kaydettik. Bu kullanılabilecek bir yöntemdir. Fakat daha yaygın ve önerilen yöntem şudur:

Kullanıcıdan bir veri aldığın zaman onu veritabanına olduğu gibi kaydet ve onu sunarken output encoding uygula. Çünkü veritabanına escape olarak kaydettikten sonra tekrar eski haline döndermesi zorudur. Ayrıca veritabanındaki değer herzaman html olarak sunulmayacak. Bazı durumlarda bir mobil uygulamya sunulacak. Bu gibi durumlarda htmlspecialchars kullanmak pek iyi bir yöntem değildir. Bunun yerine ham veriyi veritabanına kaydedip sunulan platforma göre encoding uygulamak daha iyi bir pratiktir.

Son Söz

Bu yazı ile input validation vs XSS önleme ile ilgili giriş niteliğinde bilgi vermeye çalıştım. Input sınıfını istediğiniz gibi geliştirebilir ve kullanabilisiniz. Eğer bir sorunuz yada takıldığınız bir yer varsa bana sormaktan çekinmeyin. Umarım Faydalı olmuştur.

Saygılarımla :)

Kaynakçam

all tags