C, PHP, VB, .NET

Дневникът на Филип Петров


* Code Injection – вмъкване на нежелан код

Публикувано на 07 декември 2008 в раздел ОСУП.

Вече разгледахме статии, в които описахме проблемите за вмъкване на нежелан SQL код и XSS атаки. Те всъщност са част от една по-широка общност, която можем да наречем общо "вмъкване на нежелан код". SQL injection целеше да повреди базата данни, а XSS атаките целяха да подведат потребител на системата. Логично остава още едно звено за атака - файловата система на сървъра. Знаете, че скриптовете, писани на езици като PHP, ASP, JSP, и др, се компилират и изпълняват на сървъра. Това дали означава, че тяхния сорс код е предпазен от модифициране?

Нека разгледаме няколко примера:

1. Нека разгледаме следният елементарен уебсайт:

index.php:

<?php
if (isset($_GET['page'])){
	if ($handle = @fopen($_GET['page'], "r")){
		while (!feof($handle)) {
			$line = fgets($handle);
			echo $line;
		}
		fclose($handle);
		return;
	}
	else{
		echo '404 Not Found';
		return;
	}
}
?>
<html>
<head>
	<title>WorldBank.dom code injection page</title>
</head>
<body>
	This is the index page.<br><br>
	1. <a href="index.php?page=products.html">products</a><br>
	2. <a href="index.php?page=contacts.html">contact us</a>
</body>
</html>

products.html:

<html>
<head>
	<title>WorldBank.dom products page</title>
</head>
<body>
	Hello, this is the products page!
</html>

contacts.html:

<html>
<head>
	<title>WorldBank.dom contact page</title>
</head>
<body>
	Hello, this is the contacts page!
</html>

На пръв поглед резултатите са задоволителни - ако отворите "http://worldbank.dom/index.php" ще се отвори страницата с двете хипер връзки. Ако отворите "http://worldbank.dom/index.php?page=products.html" ще се отпечата страницата с продуктите, а като отворите "http://worldbank.dom/index.php?page=contacts.html" ще се отвори страницата за контакти. Ако пък отворим несъществуващ файл, то ще се изведе "404 Not Found".

Функцията fopen обаче има функционалност, която позволява отварянето на външни ресурси. Опитайте да отворите "http://worldbank.dom/index.php?page=https://www.cphpvb.net" - с изненада ще видите, че ще се зареди информация от сайта, който четете в момента. Очевидно това е опасен източник за XSS атака, тъй като атакуващият може да зареди информация директно от трети източник.

Първото решение на проблема е да забраним allow_url_fopen в конфигурационният файл php.ini:

	allow_url_fopen = on

Второто важно нещо, за което ще се сетим, е че по този начин могат да се вмъкват каквито и да е файлове локално на сървъра. Например можете да пробвате с http://worldbank.dom/index.php?page=".htaccess" или дори http://worldbank.dom/index.php?page="../../../etc/rc.conf" или подобни. Едва ли обаче php скрипта ще има root права - по този начин ще могат да се вмъкват само и единствено файлове, до които имате достъп, т.е. по-скоро ще можете да извършите тази атака само върху файловете от самия уебсайт.

 

За да се справим с това можем да подходим по наивен начин и да търсим премахването на "лошите символи" от съдържанието на променливата:

<?php
	if (isset($_GET['page'])){
		$badChars=array("/","\\","\"",";");
		$page=str_replace($badChars,"",$_GET['page']);
		if ($handle = @fopen($page, "r")){
			while (!feof($handle)) {
				$line = fgets($handle);
				echo $line;
			}
			fclose($handle);
			return;
		}
		else{
			echo '404 Not Found';
			return;
		}
	}
?>
...

Това не е правилен подход! Вече сме споменавали, че премахването на "лошите символи" не е правилен вариант за защита. По-добрият подход е за следене за валиден формат:

<?php
	if (isset($_GET['page'])){
		if (preg_match('/(\w+)\.html$/', $_GET['page'])){
			$page=$_GET['page'];
		}
		else{
			$page = '404.html;
		}
		if ($handle = @fopen($page, "r")){
			while (!feof($handle)) {
				$line = fgets($handle);
				echo $line;
			}
			fclose($handle);
			return;
		}
		else{
			echo '404 Not Found';
			return;
		}
	}
?>
...

Накрая бихме предпочели да не рискуваме въобще с отварянето на файлове от външни източници. За целта може да използваме функцията file_exists($file), която проверява дали локален файл съществува или не:

<?php
	if (isset($_GET['page'])){
		if (preg_match('/(\w+)\.html$/', $_GET['page'])){
			$page=$_GET['page'];
		}
		else{
			$page = '404.html;
		}
		if (file_exists($page)){
			$handle = @fopen($page, "r");
			while (!feof($handle)) {
				$line = fgets($handle);
				echo $line;
			}
			fclose($handle);
			return;
		}
		else{
			echo '404 Not Found';
			return;
		}
	}
?>
...

Неразумното използване на fopen() води до нежелани XSS атаки. Показаната система е валидна само ако файловете, които добавяме са в чист HTML формат. В много случаи се налага да използваме "include" за добавяне на php код от други страници. Там проблемът вече се прехвърля от XSS атака към възможност за уязвимост на самия сървър! Представете си следният код:

<?php
	if (isset($_GET['page'])){
		// Никога не правете това!!!
		include $_GET['page'];
	}
?>

Резултатът на горният код може да бъде унищожителен за вашето приложение. Имайте предвид, че съществува конфигурационен параметър "allow_url_include", който позволява добавянето на файлове от външни източници. Отново, както в по-горният пример, би трябвало да се погрижим да отваряме само локални файлове:

<?php
	if (isset($_GET['page'])){
		if (file_exists($_GET['page'])){
			include $_GET['page'];
			return;
		}
		else{
			echo '404 Not Found';
			return;
		}
	}
?>

Чрез този пример имаме възможност за "вмъкване" на PHP код.

Трябва да споменем обаче, че представената защита (забрана на външни файлове) в никакъв случай не решава всички проблеми. Дали само "вмъкването" на външни файлове води до проблеми? Дали не съществуват и локални файлове, до които ние не бихме искали да позволяваме достъп?

Във всички примери дотук ние можем да добавяме който и да е файл в текущата директория (напр. topsecretfile.txt, config.php, и др.), дори когато "public" правата за четене и запис до него са забранени. Дори да няма такива конфиденциални данни в текущата директория - опитайте се да отворите "http://worldbank.dom/index.php?page=index.php". Ще се получи изключително неприятен безкраен цикъл. В допълнение - от последния пример можем да добавим не само файлове в текущата директория, а който и да е файл от локалната система, до който php процесът има права (напр. при лошо организирани права за достъп можем да прочетем и файлове от други директории, чрез пълния път до тях: /home/worldbank.dom/www/.passwd и т.н.).

Явно, че да правим филтриране от тип "кои файлове не трябва да се виждат" е лош подход. Значително по-добре е да направим подход "кои файлове могат да бъдат видяни". Затова нека за финал да организираме представените по-горе примери в един значително по-защитен вид:

1. Сигурно добавяне на HTML код с fopen:

<?php
	if (isset($_GET['page'])){
		$pages=array("products.html", "contacts.html");
		if (in_array($_GET['page'], $pages)){
			if (file_exists($_GET['page'])){
				$handle = @fopen($_GET['page'], "r");
				while (!feof($handle)) {
					$line = fgets($handle);
					echo $line;
				}
				fclose($handle);
				return;
			}
			else{
				echo 'File is missing<br>';
				echo 'Please contact the admin';
				return;
			}
		}
		else{
			echo '404 Not Found';
			return;
		}
	}
?>
...

2. Сигурно добавяне на PHP код с include:

<?php
	if (isset($_GET['page'])){
		$pages=array("products.php", "contacts.php");
		if (in_array($_GET['page'], $pages)){
			if (file_exists($_GET['page'])){
				include $_GET['page'];
				return;
			}
			else{
				echo 'File is missing<br>';
				echo 'Please contact the admin';
				return;
			}
		}
		else{
			echo '404 Not Found';
			return;
		}
	}
?>
...

Чрез демонстрирания метод можем да сме уверени, че могат да бъдат добавени само локални файлове и то единствено тези, които са указани в масива $pages.

Интересна част от атаката е, че с уязвима страница правеща include можете да прочетете сорс кода на php скриптовете! Ето как: http://worldbank.dom/index.php?page=php://filter/convert.base64-encode/resource=index.php

Резултатът ще бъде, че base64 кодиран сорс код на index.php файла. Видяхме, че атаките за вмъкване на нежелан код са значително по-комплексни отколото си представяхме. Филтрирането на входните данни не винаги е достатъчна защита. Винаги се стремете да създавате колкото се може по-рестриктивен режим на работа.

 



Добави коментар

Адресът на електронната поща няма да се публикува


*