Mr GET Clean

Златни правила на уеб разработчика (web developers golden rulez):

1. Няма абсолютна и универсална защита, всичко подлежи на хакване!
2. Най-добре си го хаквай {скрипта} често, често сам и се.. оправяй.. сам, преди да те оправят други!
3. Грам никакво доверие на потребителя, т.е на каквото и да е идващо от GET, POST, COOKIE, SESSION, REQUEST (ако някой все още го ползва), HTTP Headers, etc.
4. 13 проверки са по-добре от една или николко, но все пак не се знае дали са достатъчни..
5. Ученето трябва да продължи!

Във връзка с една тема от миналата седмица в BGDev, ето един метод за почистване на GET заявки от излишни боклуци.

В заявката са допустими само параметри, дефинирани в масива $allowableParams и са забранени символите, посочени в другия conf масив: $forbiddenSymbols.

Разбира се, подобна реализация има своите ограничения (modRewrite, кирилица, search формуляри, събмитващи през GET и т.н.), така че в зависимост от приложението може да се променя съдържанието на масивите и изобщо цялата реализация на метода. Също така, при установяване на непозволени параметри или забранени символи (опити за XSS или SQL injection), освен логването на опита и редиректването към страница с “чиста” GET заявка, може да се добави и друга някаква функционалност: мейл до администратор, бан на потребител и т.н.

Така, ето как изглежда началото на една страница, която ще използва този клас:

< ?php
define('CLASSES_PATH', ".");

$forbiddenSymbols	= array('<', '>', '(', ')',
	'..', '%', '*', '+', '!', '@');
$allowableParams	= array('p', 'id');

if (isset($_SERVER['QUERY_STRING']) &&
	!еmрty($_SERVER['QUERY_STRING'])) {

	require_once(CLASSES_PATH . "/class.clean.php");
	$clean = new MrGETClean;

	$queryString = $clean->queryWalk($_SERVER['QUERY_STRING'],
		$forbiddenSymbols,
		$allowableParams,
		$_SERVER['SCRIPT_NAME']);

	if ($queryString != $_SERVER['QUERY_STRING']) {
		$newURL = basename($_SERVER['SCRIPT_NAME']) .
			'?' . $queryString;
		header("location: " . $newURL);
	}

}

Ето го и самия клас с основния метод queryWalk:

< ?php
define('LOG_PATH', "log");

class MrGETClean {

function queryWalk($queryString,
	$forbiddenSymbols,
	$allowableParams,
	$scriptURL) {

	$flag = false;

	$queryFragments			= array();
	$queryArr				= array();
	$cleanedQueryFragments	= array();

	$queryFragments = explode("&", $queryString);

	foreach ($queryFragments as $key => $value) {
		$queryArr[$key]['p'] =
			substr($value, 0, strpos($value, '='));
		$queryArr[$key]['v'] =
			substr($value, strpos($value, '=') + 1);
	}

	foreach ($queryArr as $key => $value) {
		if (!in_array($queryArr[$key]['p'],
				$allowableParams)) {
			$queryArr[$key] = null;
			$flag = true;
		}

		foreach ($forbiddenSymbols as $symbol) {
			if (stristr($queryArr[$key]['v'],
					$symbol)) {
				$queryArr[$key] = null;
				$flag = true;
			}
		}
	}		

	foreach ($queryArr as $key => $value) {
		if ($queryArr[$key]['p'] != null) {
			$cleanedQueryFragments[$key] =
				$queryArr[$key]['p'] . "=" .
				$queryArr[$key]['v'];
		}
	}

	if ($flag) {
		$this->MrGETCleanMakeLog($queryString,
			$scriptURL);
	}

	$queryString = implode("&", $cleanedQueryFragments);

	return $queryString;

}

А това са методът, който генерира лог файла и този, който връща IP адреса на “атакуващия”:

function MrGETCleanMakeLog($queryString, $scriptURL) {

	$date = date ("d-m-Y @ H:i:s");
	$log = LOG_PATH . "/log.txt";
	$handle = fopen($log,"a+" );
	fputs ($handle, 'Attack Date: ' . $date .
	' | Attacker IP: ' . $this->getIP() .
	' | Query: ' . htmlentities(urldecode($queryString)) .
	' | Script: ' . $scriptURL . "\r\n");
	fclose($handle);

}

function getIP() {

	$IP = '';

	if (isset($_SERVER)) {
		if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
			$IP = $_SERVER['HTTP_X_FORWARDED_FOR'];
		}
		elseif (isset($_SERVER['HTTP_CLIENT_IP'])) {
			$IP = $_SERVER['HTTP_CLIENT_IP'];
		}
		else {
			$IP = $_SERVER['REMOTE_ADDR'];
		}
	}
	else {
		if (getenv('HTTP_X_FORWARDED_FOR'))	{
			$IP = getenv('HTTP_X_FORWARDED_FOR');
		}
		elseif (getenv('HTTP_CLIENT_IP')) {
			$IP = getenv( 'HTTP_CLIENT_IP' );
		}
		else {
			$IP = getenv('REMOTE_ADDR');
		}
	}

	return $IP;

}

Файловете, които са ви нужни са примерният index.php и класът class.clean.php. Демото можете да пробвате тук: www.vlkomarov.info/projects/mr-get-clean (Log: log.txt).

Променяйте съобразно вашите нужди и спецификата на приложението.

Линкването на това демо не е задължително, но ще съм ви благодарен, ако го правите ;)

Tags: , , , , , ,


Този плъгин ме дразни. Не стига, че не оцветява контекстно сорс кода така, както се очаква, но и с това пренасяне на нов ред за да се побера в 500px на блога прецаквам всякаква идея за четимост и нормални indent-и.

A BGDev в момента е паднал (?) и не мога да линкна темата, заради, която писах всичко това.. Трябва да намеря и примера, който послужи за основа на този клас (например, метода за логването е почти същия. getIP() e от php.net.