Написание безопасных web-приложений

Статус
В этой теме нельзя размещать новые ответы.

Black#FFFFFF

Постоялец
Регистрация
19 Июл 2007
Сообщения
228
Реакции
172
Написание безопасных web-приложений.
Содержание:
  • Аннотация или суть проблемы
  • Типы инъекций
    • Code injection
    • SQL injection
    • XSS injection
  • Советы по проектированию безопасного web приложения.
    • Избавляемся от Code injection
    • Избавялемся от SQL injection
    • Сообщения об ошибках
    • Обработка данных
    • Оператор Like
    • Избавляемся от XSS injection
    • Проверка файлов при загрузке данных по http
    • Mysqli параметризированные запросы, placeholders

Аннотация или суть проблеммы:
Приходится писать много кода. Иметь дело с огромным количеством вэб приложений. К сожалению, раз за разом среди совсем различных скриптов встречаю одни и те же ошибки, которые приводят к большим проблемам безопасности в среде разрабатываемого вэб приложения. Это и типичные ошибки, связанные с отсутствием или недостаточной фильтрацией данных, передаваемых от пользователя на сервер, и предоставление отладочной информации, информации об ошибках приложения и предупреждениях прямо в браузер на клиентской вэб странице, и недостаточная проверка содержимого файлов, загружаемых через POST.

Целью этой статьи будет вкратце ознакомить Вас с основными видами угроз безопасности web приложений и привести пример построения защиты стандартными средствами функций обработки данных и настройки выполнения скриптов в среде приложения, использующей для своего функционирования PHP 4+ Mysql 3.2+.

В основном благодаря различного рода уязвимостям и предоставлению отладочной информации на страницах поддерживаемого или разрабатываемого Вами вэб приложения удаленный пользователь, выполнив определенные действия, после поиска уязвимостей, сможет получить доступ практически к любой информации, циркулирующей внутри вашего скрипта, речь здесь ведется не только о нанесении урона конфиденциальности информации, как например потери данных о кредитных карточках, если мы имеем дело с интернет магазином, но и о возможности полного уничтожения вэб приложения и результатов его работы.

Согласитесь, не слишком радужная перспектива?

Примечание:
Статья от Вас потребует определенного уровня знаний. Вы должны быть как минимум поверхностно знакомы с базами данных и синтаксисом запросов SQL, иметь представление о работе с функциями в языке PHP, и о разметке HTML страниц, а так же о способе передачи информации от клиента к серверу.

Начнем с обзора основных уязвимостей:

Итак, какие виды уязвимостей бывают, об этом было довольно много сказано.
Рассмотрим три основные из них: Сode injection (php including), sql injection, xss scripting. (Краткая информация с описанием инъекций размещенная и дополненная здесь, взята со страниц журнала Для просмотра ссылки Войди или Зарегистрируйся Внутримышечно и внутривенно, Спецвыпуск: Хакер, номер #075, стр. 030, Обзор технологий взлома веб-ресурсов).
Сode injection
В теории все выглядит просто: есть скрипт, исполняемый на сервере, в который взломщику необходимо встроить свой код. Провернуть такую махинацию довольно просто, если соблюдены два условия. Во-первых, веб-разработчик должен использовать конструкцию include с параметром переменной, а во-вторых, должен плохо проверять данные, поступающие от пользователя. Наиболее часто такая ситуация возникает в простых скриптах:

Язык: HTML + PHP
PHP:
<!—- Заголовок -->
<?php
include ($page);
?>
<!—- Завершающая часть -->

Соответственно, работа идет со ссылками типа Для просмотра ссылки Войди или Зарегистрируйся. Самое безобидное, что можно сделать – это просмотреть информацию о PHP (и не только:(

Листинг
Язык: PHP
PHP:
<?php
phpinfo();
?>

Создав такой файл у себя на сервере, просто включаем его в запрос вместо about.php – и видим всю информацию на экране. Таким образом, мы внедрили произвольный код в скрипт на сервере и получили необходимую информацию. Но это только цветочки, ведь можно при помощи PHP сделать все, что нашей душе угодно.

SQL Injection

Давным-давно, когда по земле еще ходили динозавры, веб-программеры использовали для хранения данных текстовые файлы. Потом, когда человек изобрел колесо, веб-разработчики придумали базы данных и стали хранить все в них. И было всем счастье – и список пользователей туда засунуть можно, и все документы на сайте положить. Но однажды один умелец случайно ввел в форму апостроф, и выдал скрипт SQL-ошибку. Прочитал умелец сообщение, подумал немного и ввел вместо апострофа

Листинг
• Язык: SQL
Код:
' OR '1'='1’

еще немного поколдовал и стал с тех пор администратором. Работа с базой ведется на языке SQL, например, чтобы проверить, что пользователь существует, можно сделать такой запрос по логину:

Листинг
• Язык: SQL
Код:
' SELECT * FROM users WHERE username='$username'

А теперь подставь в запрос апостроф или ' OR '1'='1’ и посмотри, что получится. Фактически, мы можем выполнить произвольный SQL-запрос, и случиться что-нибудь нехорошее:

Листинг
• Язык: SQL
Код:
' '; DELETE FROM customers WHERE 1 or username = '

Хочу кинуть еще пару хороших идей, как можно воспользоваться SQL-инъекцией. Для этого мы рассмотрим детали диалектов языка SQL у разных производителей. Начнем с MySQL, который делает то, что я от него ни как не ожидал ;). А проблема проста: если SELECT-запрос подвержен инъекции, то не факт, что его результат будет выведен на экран, но если внимательно почитать руководство по MySQL, то можно найти замечательный функционал – результат запроса можно перенаправить в файл!

Листинг
• Язык: SQL
Код:
SELECT <поля> FROM <таблица> INTO OUTFILE '<файл>';

Теперь осталось найти каталог, который нас приютит. В случае с CMS все решается довольно просто – почти всегда есть каталог для загрузки файлов upload. Именно в такой каталог и стоит перенаправлять вывод.
Начиная с четвертой версии, MySQL поддерживает объединение запросов при помощи команды UNION. Таким образом, можно вывести дополнительную информацию из произвольной таблицы. Посмотрим, как это выглядит на примере:

Листинг
• Язык: SQL
Код:
SELECT title, description FROM articles WHERE id=’$id’;

Title и description имеют тип varchar, поэтому переменной $id нужно присвоить такое значение, чтобы при подстановке получился следующий запрос:

Листинг
• Язык: SQL

Код:
SELECT title, description FROM articles WHERE id=’123123’
UNION
SELECT login, password FROM users;
/*
‘;

Обрати внимание, как я использовал комментарий – при Union-инъекциях – это стандартная практика отсечения ненужной части строки.

Примечание:

На самом деле здесь требуется выполнение еще одного условия, чтобы подобрать нужный тип запроса для SQL инъекций, необходимо чтобы разработчик использовал вывод сообщения об ошибках в стандартный поток браузера. Что практикуется очень часто.

Пример:

PHP:
<?php 
$q = 'SELECT * FROM `bd` WHERE `id`='.$_GET['id'];
$r = mysql_query(q);

if(!is_resource($r)){ //!$r || mysql_error()
die(mysql_error()); //echo(mysql_error()); 
}
?>

Примерно вот так разработчик «выводит» возникшую при запросе к базе данных ошибку в браузер, что, зачастую, помогает злоумышленнику, предоставляя необходимую информацию для составления запроса с SQL injection.

XSS

В наше время на большинстве сайтов пользователи имеют возможность создавать свои материалы и комментировать чужие. Для красивого оформления пользователю дается возможность вводить данные в формате HTML. Ввод HTML-кода может быть разрешен напрямую, либо при помощи WYSIWYG-редактора. Казалось бы, все довольны! Особенно взломщики ;). Есть хорошая пословица: «Где HTML, там и JavaScript». Таким образом, при вводе можно использовать тег <script> для исполнения произвольного JavaScript (и не только его, кстати). А если мы можем использовать скрипты, которые исполняются на стороне пользователя, значит, мы можем украсть его куки! А тут уже и до взлома аккаунта недалеко…

Примечание:

Здесь не все так просто, получить можно не только cookie клиента, но можно изменить поведение клиентской страницы буквально произвольно, по желанию, от нарушения дизайна сайта, дефейса уязвимого сайта, вплоть до полного перенаправления всей информации с сайта источника на другой сайт. И дело не только в javascript, например без использования javascript вполне возможно добавить <iframe>, <frameset> и многие другие не желательные теги, включить flash тэги <embeded>, <object> и апллеты в html в содержимое страницы.

Фрагментированные XSS-атаки

Часто бывает так, что информация, вводимая пользователем, хорошо фильтруется, и к форме на кривой козе не подступишься. Но если в ней несколько полей, например, «название материалов» и «содержание материалов», то можно попробовать более сложный вид атаки – фрагментированный. При таком подходе, необходимо, чтобы данные из разных полей формы выводились последовательно, и тогда появляется вероятность того, что вместе «безобидные строчки» станут грозным оружием.


Советы по проектированию безопасного web приложения.

Избавляемся от Code injection:

Как обезопаситься? Здесь все просто. Передавайте через GET,POST массивы скрипты, которые нужно подключить в данном приложении, не напрямую, а косвенно, через определенную переменную, и, на основании ее значения, подключайте тот или иной скрипт, используя include или require.

Пример:

Нам нужно подключить содержимое файла example.php в index.php.
Передаем в GET переменную file значение 1.
Index.php?file=1
Код файла index.php:

PHP:
<?php
	$file = intval($_GET['file']);
	if(isset($file) && !empty($file)){
   	switch($file){
case 1:
 include($_SERVER['DOCUMENT_ROOT'].'/ example.php');
break;
//case 2 case 3…. И так далее перечисление других подключаемых файлов
default:
break;
}            
}
?>

Замечание:

Иногда угроза CODE injection возникает еще по одной причине. Некоторые начинающие вэб разработчики подключение самых обыкновенных файлов используют с помощью include,require, либо стараются придать конфигурационным файлам отличное от php расширение, .ini и прочее.
Предостерегаю вас от подобных ошибок. Для функции работы с файлами, пожалуйста, используйте набор стандартных функций для получения информации из файлов (file,file_get_contents,fread,fgetc,fpassthru,fgetss). Но не в коем случае не выполняйте их содержимое, как код PHP. Для конфигурационных файлов пожалуйста сохраняйте их с расширением .php. Web сервер сам поможет Вам скрыть содержимое данных файлов от просмотра через web браузер.

Избавляемся от SQL injection.

Первая проблема: Вывод информации об ошибках базы данных в браузер пользователя.
Нам нужно определиться, когда этап отладки миновал, стоит ли нам информацию об ошибках SQL, возникшую при работе скриптов, выводить в браузере клиента? По-моему нет. Но иногда требуется все же иметь доступ к этой информации, которая во многом в процессе работы приложения помогает устранять ошибки и недостатки. Как же быть?
Казалось бы, выход очевиден.
Используем попытку запрета вывода об ошибках вообще:

PHP:
<?php 
ini_set('display_errors','off');
error_reporting(0);
?>

Но, ошибки базы данных, выводимые в браузер разработчиками с помощью
PHP:
echo(mysql_error()); die(mysql_error())
всеже останутся.
Не следует действительно писать:

PHP:
<?php
echo(mysql_error());
die(mysql_error())
?>

PHP предоставляет для этих случаев функцию trigger_error.

bool trigger_error ( string $error_msg [, int $error_type= E_USER_NOTICE ] )
string $error_msg – наша ошибка, например mysql_error()
int $error_type= E_USER_NOTICE
может принимать значение трех констант
E_USER_WARING,E_USER_NOTICE,E_USER_ERROR
Итак, перепишем наше сообщение об ошибках в одном из примеров с использованием данной функции:
Пример приведенный выше:

PHP:
<?php 
$q = ‘SELECT * FROM `bd` WHERE `id`=’.$_GET[‘id’];
$r = mysql_query(q);

if(!is_resource($r)){ //!$r || mysql_error()
trigger_error(mysql_error(),E_USER_ERROR); 
}
?>

Результат работы в итоге будет тот же самым, скрипт завершит свое действие в той же строке при возникновении ошибки запроса, но с одним существенным отличием, теперь мы можем перехватить содержимое ошибки, и отправить его в лог файл ошибок.

Совет:

Директорию для хранения файлов с ошибками лучше всего определять за пределами директории вэб сервера. Либо ограничить доступ к ней с помощью .htaccess, или же хотя бы файлам с ошибками присвоить расширение .php и первой строчкой каждого файла сделать:

PHP:
<?php die("");?>

Чтобы было невозможно со страниц сайта прочесть содержимое файла, который будет нести столь важную информацию.

Вопрос: почему же перехватывать сообщения об ошибках именно в файл? Не в базу.

Ответ: ошибки могут возникнуть еще при подключении к базе данных, и их нужно корректно обработать и перехватить и на этом этапе тоже.

Чтобы перехватить вывод ошибок из браузера в файл мы воспользуемся следующими функциями.
mixed set_error_handler ( callback $error_handler [, int $error_types= E_ALL | E_STRICT ] )
Первым параметром функции set_error_handler указываем свою функцию перехватчик ошибок, вторым необязательным параметром указываем типы перехватываемых ошибок.

Функция, которая будет перехватывать ошибки, указываемая в параметре callback $error_handler должна принимать следующие параметры:
$error_num – номер ошибки,
$error_var – описание ошибки
$error_file – где произошла ошибка,
$error_line – строка, где произошла ошибка

Вот сокращенный пример перехвата ошибок в файл, работающий в нескольких моих вэб проектах. Добавляем этот код с первым подключаемым на каждой странице файлом, и тем самым организуем перехват ошибок в файлы. Кроме этого с ошибкой можно логировать достаточно много интересной информации, как например у меня – ай пи пользователя, страница, с которой он пришел на эту страницу, если таковая есть, время, когда произошла ошибка, и прочее.
Функциональность этого скрипта можно улучшить в сторону отсылки лога ошибок на указанный емейл, архивирования логов ошибок при достижении определенного размера файла ошибок и т.п.

PHP:
<?php

define ('ROOT_PATH', $_SERVER['DOCUMENT_ROOT']."/");//корневая директория для файлов с ошибками
define('_ERR_HANDLING',true); //запретить - true/разрешить – false вывод ошибок в браузер
define('_ERR_DIR',ROOT_PATH.'/errs/'); //где будем хранить файлы ошибок, в какой папке?
err_handler();

function err_handler(){
		
		
		if(_ERR_HANDLING){
		
		$error_reporting						= '';	
		$error_reporting						= ini_get('error_reporting');
		$error_reporting						= $error_reporting?$error_reporting:E_ALL;
		
		
		error_reporting(E_ERROR);
		
			
			
		$date_file 								= date('dmY').'.php';
		$dir									= _ERR_DIR;
		$path									= $dir.$date_file;
		$logfile								= '';


		if(!is_dir($dir) || !is_writable($dir)){
			
			if(is_dir($dir)&&!is_writable($dir)){
				
				chmod($dir,0775);
				
			} else if(!is_dir($dir)){
			
				$isdir							= false;
				$isdir							= mkdir($dir,0775);
			
			}
			
			if(!$isdir&&!is_writable($dir)){
				$dir							= ROOT_PATH;
				$path							= $date_file;	
			}
		}


		if(is_dir($dir) && is_writable($dir)){
	

		if(!is_file($path)){
			
			
			$fp			= fopen($path,'w+');
			if($fp && is_resource($fp)){
				
				$secuire	= '<?php die("Forbidden."); ?>';
				flock($fp,LOCK_EX);
                fwrite($fp,$secuire."\n");
                flock($fp,LOCK_UN);
                fclose($fp);
                $fp		= null;
                
				
						unset($secuire);
			}
		}	
		
		
		
		if(is_file($path) && !is_writable($path)){
			
				chmod($path,0775);
		
		}
		
		if(is_file($path) && is_writable($path)){
			
		ini_set('display_errors',0);
		set_error_handler('error_reporting_log', (E_ALL & ~E_NOTICE));
		$logfile							= $path;
				
			define('LOG_FILE',$logfile);
		
		}
			
				unset($date_file,$dir,$path,$logfile);
		
		}
		
}

		error_reporting($error_reporting);
		
		unset($error_reporting);

}


function error_reporting_log($error_num, $error_var=null, $error_file=null, $error_line=null) {
 				
            			
						
						$error_desc		= '';
						$error_desc  	= 'Error';
						
						switch ($error_num){
							case E_WARNING:
								$error_desc = 'E_WARNING';
								break;
							case E_USER_WARNING:
								$error_desc = 'E_USER_WARNING';
								break;
							case E_NOTICE:
								$error_desc = 'E_NOTICE';
								break;
							case E_USER_NOTICE:
								$error_desc = 'E_USER_NOTICE';
								break;
							case E_USER_ERROR:
								$error_desc = 'E_USER_ERROR';
								break;
							default:	
								$error_desc  = 'E_ALL';
								break;
							
						}
						
						$date_file 		= date('y-m-d H:I:S');
						$logfile		= LOG_FILE;
                        $url 			= $_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
                        $date_time 		= date('d.m.y - H:i:s');
						$ip				= $_SERVER['REMOTE_ADDR'];
                        $from			= isset($_SERVER['HTTP_REFERER'])&&!empty($_SERVER['HTTP_REFERER'])?$_SERVER['HTTP_REFERER']:'';
                        
                        
                        $errortext 		= $error_desc.': '.$error_var."\t".'Line: '.$error_line."\t".'File: '.$error_file."\t".'Link: '.$url."\t".'Date: '.$date_time."\t".'IP: '.$ip."\t".' FROM:'.$from."\n";

                        				unset($from,$error_desc,$error_var,$error_line,$error_file,$url,$date_time,$error_write);
                        
                        $secuire		= '<?php die("Forbidden."); ?>';
                        
                        if(is_file($logfile)&&is_writeable($logfile)){
                        	$strings	= file($logfile);
                        	if(isset($strings[0])&&!empty($strings[0])&&strpos($strings[0],$secuire)===false){
                        		unlink($logfile);
                        	}
                                unset($strings);
                        }
                        
                        if(!is_file($logfile)){
                        	$dir		= dirname($logfile);
                        	if(is_dir($dir)&&is_writable($dir)){
                        	$fp	  	   	= fopen($logfile,'w+');
                        				if(is_resource($fp)){
                        					flock($fp,LOCK_EX);
                        					fwrite($fp,$secuire."\n");
                        					flock($fp,LOCK_UN);
                        					fclose($fp);
                        					$fp		= null;
                        				}		
                        							unset($dir,$fp);
                        	}
                        }
                        							unset($secuire);
                        if(is_file($logfile)&&!is_writable($logfile)){
                        	chmod($logfile,0775);
                        }
                        
                        if(is_file($logfile)&&is_writeable($logfile)){
                        	
                        	$fp	  	   	= fopen($logfile,'a+');
                        	if(is_resource($fp)){
                        		flock($fp,LOCK_EX);
                        		fwrite($fp,$errortext);
                        		flock($fp,LOCK_UN);
                        		fclose($fp);
                        		$fp		= null;
                        				unset($fp);
                        	}
                        
                        }
                        				unset($logfile);
                        
                
                return true;
}


?>

Мне могут возразить, что на самом деле на серверах ведутся access_log, error_log логи доступа и ошибок вэб приложений. Но, не всегда есть доступ к этой информации, и свой перехватчик ошибок можно настроить гораздо гибче на предоставление именно той информации, которая нам нужна более всего.
Итак, после включения логирования информации и изменении вывода информации об ошибках в виде
PHP:
trigger_error(mysql_error(),E_USER_ERROR);
на странице пользователя больше не отобразится информация о ошибке mysql, что добавит работы хакеру при выявлении возможности sql injection.

А в папке с ошибками появится файл с именем в виде текущего числа и содержащий следующую информацию:

PHP:
<?php die("Forbidden"); ?>
E_USER_WARNING: MySQL error: 1064		You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')' at line 1		Query:SELECT * FROM mybb_attachments WHERE pid IN ()	Line: 348	File: /forum/inc/db_mysql.php	Link: */forum/archive/index.php/thread-796.html	Date: 27.02.09 - 07:23:07	IP: 66.249.72.99	 FROM: http://www.ya.ru

Замечание:

Правда не все ошибки можно перехватить в PHP, например фатальные ошибки E_ERROR без хаков перехватить не получится.

Вторая проблема: недостаточная обработка данных перед помещением в базу данных.

Собственно здесь и возникают те самые ошибки, которые могут помочь удаленному пользователю произвести SQL injection.

Чтобы этого избежать советую перед помещением данных в запрос к базе применять фильтрацию данных.
Какие функции нам здесь могут помочь при проверке переменных перед построением запроса к базе данных?
Функции:

Для проверки существования непустых данных:

bool isset ( mixed $var [, mixed $var [, $... ]] )
Устанавливает определена ли переменная.

bool empty ( mixed $var )
Если переменная не должна быть пустой или не должна принимать отрицательное нулевое значение.

Для обработки целочисленных данных:

Функция

int intval ( mixed $var [, int $base ] )
Возвращает целое значение переменной var , используя указанное основание системы исчисления base для преобразования (основание по умолчанию 10).

bool is_numeric ( mixed $var )

Проверяет, является ли данная переменная числовой.

Если данные должны быть еще к тому же исключительно положительны (например: уникальный столбец в базе данных с атрибутом AUTO_INCREMENT) советую использовать:

number abs ( mixed $number )

Возвращает абсолютное значение number . Если number имеет тип float, возвращаемое значение также будет иметь тип float, иначе - integer.

Для обработки строковых данных:

Функция
string trim ( string $str [, string $charlist ] )

Удаляет лишние пробельные последовательности символов из начала и конца строки.

Функция string mysql_real_escape_string ( string $unescaped_string [, resource $link_identifier ] )

Советую обрабатывать данные этой функцией перед помещением их непосредственно в запрос к базе данных. Предпочтительнее использовать ее, нежели addslashes или доморощенные аналоги, дело в том, что данная функция корректно определяет кодировку подключения к mysql базе данных.

Но здесь не все так просто, нужно также будет избежать двойного экранирования строки. Воистину говорится «дорога в Ад вымощена благими намерениями». Так вот, чтобы сделать автоматическим экранирование данных разработчики PHP добавили такую возможность как magic quotes – или автоматическое экранирование данных. Эта возможность будет полностью исключена в 6 версии PHP. А пока с ней не знают что и делать. Часть хостингов ее отключает. Часть – нет. В чем же суть проблемы:
К данным, которые пришли в приложение из post, get массивов, если magic_quotes_gps = on применяется автоматически функция addslashes:

string addslashes ( string $str )

Возвращает сроку str , в которой перед каждым спецсимволом добавлен обратный слэш (\).

Пример:
Index.php?info=hi’there

В скрипте при включенном режиме magic_quotes_gps = on значение переменной $_GET[“info”] будет равно “hi\’there”

Добавится лишний \ бэк слэш. И если перед помещением данных в базу данных мы обработаем их с помощью mysql_real_escape_string то будет добавлен лишний слэш $_GET[“info”] будет равно “hi\\’there”
, что приведет к возникновению ошибки на стадии запроса.

Здесь нам поможет функция:

int get_magic_quotes_gpc ( void )

Которая вернет 1 если magic_quotes_gps включен. И мы сможем исключить лишние слэши из значения переменной с помощью функции:
string stripslashes ( string $str )

Удаляет экранирующие бэкслэши.
Index.php?info=hi’there

PHP:
<?php 
$magic_quotes = get_magic_quotes_gpc()?1:0;
$info	            = $_GET['info'];
if($magic_quotes){
$info              = stripslashes($info);
}
$info               = mysql_real_escape_string($info);
?>

Примечание:
По существу magic_quotes могут испортить мультибайтовые данные в кодировке UTF-8, приходящие из клиента, приняв часть мультибайтной строки за спецсимвол. Эту директиву можно попробывать отключить напрямую из скрипта:
ini_set('magic_quotes_gpc',0);
Или используя, если разрешено хостером, .htaccess:
php_flag magic_quotes_gpc off

Кроме этого см. magic_quotes_runtime, get_magic_quotes_runtime(), set_magic_quotes_runtime(0)

Примечание:
В PHP до версти 4.3.0 аналог функции mysql_real_escape_string назывался mysql_escape_string.

Можно написать следующую функцию для проверки какая из функций поддерживается и с учетом всего вышесказанного:

PHP:
<?php

function safe_sql($param){
 if(is_array($param)){
  
 $param					= array_map('safe_sql',$param);
 return 				$param;

} else if(is_string($param)){

$magic_quotes			=         get_magic_quotes_gpc()?1:0;
if($magic_quotes){
	$param				= stripslashes($param);
};
unset($magic_quotes);
	
if(function_exists('mysql_real_escape_string')){
  $param = mysql_real_escape_string($param);
} else if(function_exists('mysql_escape_string')){
  $param = mysql_escape_string($param);
} 
  return 				$param;
} else {
  return $param; //не строка и не массив?
};
}

$info					= safe_sql($_GET['info']);

?>


Особенности работы с оператором LIKE
(Особенности работы с оператором LIKE по материалам интернета, статья Для просмотра ссылки Войди или Зарегистрируйся)

Совершенно отдельный случай - оператор LIKE.
Во-первых, помимо обычного добавления бэк слеш, в переменных, которые подставляются в LIKE, надо удваивать слеши. То есть, если в переменной содержится символ \, то его надо удвоить, а после этого выполнить обычное добавление бэк слеш, через mysql_real_escape_string.
К примеру, если мы ищем строку:
Код:
символ \ называется "backslash"

и нам нужно точное совпадение, мы просто применяем mysql_real_escape_string и запрос получается стандартный:
Код:
SELECT * FROM test WHERE field = 'символ \\ называется \"backslash\"'

Если же мы хотим подставить эту строку в LIKE, то сначала надо заменить каждый слеш на два, а потом применить mysql_real_escape_string. В результате получится:
Код:
SELECT * FROM table WHERE field LIKE '%символ \\\\ называется \"backslash\"%'

Во-вторых, следует обратить внимание на то, что ни одна из функций, добавляющих слеши, не добавляет их к метасимволам поиска "%" и "_", используемым в операторе LIKE. Поэтому, если вы используете этот оператор, и не хотите, чтобы символы _ и % использовались, как маски, то добавляйте слеши вручную. Это можно сделать командой:
PHP:
<?php
$data = addCslashes($data, '%_');
?>
Внимание - это не addslashes! В имени этой функции есть дополнительная буква "c".

Таким образом получается, что переменные, используемые в операторе LIKE мы должны обрабатывать отдельно.
Сначала заменять один слеш на два, с помощью такого, к примеру, кода:


PHP:
<?php
$var=str_replace('\\','\\\\',$var);
?>
затем (можно наравне со всеми другими данными, идущими в запрос) прослешиваем:

PHP:
<?php
$var=mysql_real_escape_string($var);
?>
а затем, если хотим, чтобы _ и % соответствовали точно самим себе, делаем

PHP:
$var=addCslashes($var, '_%');

В результате, если мы будем искать, к примеру, такую строку
символ \ называется "backslash", а символ _ называется "underscore"
то после обработки, в запросе она должна выглядеть так:
'%символ \\\\ называется \"backslash\", а символ \_ называется \"underscore\"
То есть, слеш, который был в строке изначально - учетверился. Остальные символы прослешились, как обычно. Плюс - прослешился символ подчёркивания.


Избавляемся от XSS injection:

Какие функции могут помочь здесь?
string strip_tags ( string $str [, string $allowable_tags ] )
Эта функция возвращает строку str , из которой удалены HTML и PHP тэги.
Необязательный второй аргумент может быть использован для указания тэгов, которые не должны удаляться.

string htmlspecialchars ( string $string [, int $quote_style [, string $charset ]] ) – переводит в HTML -сущности амперсанд, кавычки, апостроф, знаки «больше» и «меньше».
string htmlentities ( string $string [, int $quote_style [, string $charset ]] ) – аналог htmlspecialchars, но использует HTML, -сущности
string html_entity_decode ( string $string [, int $quote_style [, string $charset ]] )
html_entity_decode(), в противоположность функции htmlentities(), Преобразует HTML сущности в строке string в соответствующие символы.

Рассмотрим самый общий вариант, получение исключительно текстовой информации из браузера клиента.

PHP:
<?php


function get_text($param,$enc = 'UTF-8'){
	
	$param			 = html_entity_decode($param,ENT_QUOTES,$enc);
	$param                     = preg_replace('/&#(\d+);/me', "chr(\\1)", $param); // decimal notation
	// convert hex
	$param                     = preg_replace('/&#x([a-f0-9]+);/mei', "chr(0x\\1)", $param); // hex notation
        // convert decimal
	$param			 = strip_tags($param);
	
 
        $param		 	= trim($param);
	
           
           //$param = nl2br($param);
            return $param;
}
$param				= get_text($_POST[‘param’]);

?>

Примечание: данные, обработанные на предмет XSS инъекций все равно требуют обработки функцией mysql_real_escape_string перед формированием запроса к базе данных.

Иногда требуются более сложные фильтры содержимого, когда нужна гибкая обработка данных и мы должны «пропустить» нужный нам HTML контент, отфильтровав нежелательный .

Здесь требуется очень тщательно спланировать и задать себе хотя бы несколько подобных вопросов:

  • Какие данные являются допустимыми (тэги и прочее)
  • Какие атрибуты этих данных требуют дополнительной проверки (как например свойства src тэгов
iframe, img, href – тэга a, которые могут содержать включения вида выполнения кода javascript: )[/LIST]
  • Проверка данным на соответствие определенным критериям (как максимальная длинна строки и т.п.)
  • Обработка данных для безопасного хранения в составе базы данных

И в конце приведу как пример обработки данных для исключения xss угроз класс InputFilter использующийся в репозитарии cms Joomla v 1.5.9 (пример класса видоизменен для использования без пакета cms Joomla - можете брать и использовать в других своих проектах;) :(

PHP:
<?php 
/**
 * @class: InputFilter (PHP4 & PHP5, with comments)
 * @project: PHP Input Filter
 * @date: 10-05-2005
 * @version: 1.2.2_php4/php5
 * @author: Daniel Morris
 * @contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris
 * Tobin and Andrew Eddie.
 *
 * Modification by Louis Landry
 *
 * @copyright: Daniel Morris
 * @email: dan@rootcube.com
 * @license: GNU General Public License (GPL)
 */

class InputFilter
{
	var $tagsArray; // default = empty array
	var $attrArray; // default = empty array

	var $tagsMethod; // default = 0
	var $attrMethod; // default = 0

	var $xssAuto; // default = 1
	var $tagBlacklist = array ('applet', 'body', 'bgsound', 'base', 'basefont', 'embed', 'frame', 'frameset', 'head', 'html', 'id', 'iframe', 'ilayer', 'layer', 'link', 'meta', 'name', 'object', 'script', 'style', 'title', 'xml');
	var $attrBlacklist = array ('action', 'background', 'codebase', 'dynsrc', 'lowsrc'); // also will strip ALL event handlers

	/**
	 * Constructor for inputFilter class. Only first parameter is required.
	 *
	 * @access	protected
	 * @param	array	$tagsArray	list of user-defined tags
	 * @param	array	$attrArray	list of user-defined attributes
	 * @param	int		$tagsMethod	WhiteList method = 0, BlackList method = 1
	 * @param	int		$attrMethod	WhiteList method = 0, BlackList method = 1
	 * @param	int		$xssAuto	Only auto clean essentials = 0, Allow clean
	 * blacklisted tags/attr = 1
	 */
	function inputFilter($tagsArray = array (), $attrArray = array (), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1)
	{
		/*
		 * Make sure user defined arrays are in lowercase
		 */
		$tagsArray = array_map('strtolower', (array) $tagsArray);
		$attrArray = array_map('strtolower', (array) $attrArray);

		/*
		 * Assign member variables
		 */
		$this->tagsArray	= $tagsArray;
		$this->attrArray	= $attrArray;
		$this->tagsMethod	= $tagsMethod;
		$this->attrMethod	= $attrMethod;
		$this->xssAuto		= $xssAuto;
	}

	/**
	 * Method to be called by another php script. Processes for XSS and
	 * specified bad code.
	 *
	 * @access	public
	 * @param	mixed	$source	Input string/array-of-string to be 'cleaned'
	 * @return mixed	$source	'cleaned' version of input parameter
	 */
	function process($source)
	{
		/*
		 * Are we dealing with an array?
		 */
		if (is_array($source))
		{
			
			$source 	=  array_map( array( 'InputFilter', 'process' ), $source );
			return $source;
		
		} else
			/*
			 * Or a string?
			 */
			if (is_string($source) && !empty ($source))
			{
				// filter source for XSS and other 'bad' code etc.
				$source				=  $this->remove($this->decode($source));
				
				return $source;
			
			} else
			{
				/*
				 * Not an array or string.. return the passed parameter
				 */
				return $source;
			}
	}

	/**
	 * Internal method to iteratively remove all unwanted tags and attributes
	 *
	 * @access	protected
	 * @param	string	$source	Input string to be 'cleaned'
	 * @return	string	$source	'cleaned' version of input parameter
	 */
	function remove($source)
	{
		$loopCounter = 0;
		/*
		 * Iteration provides nested tag protection
		 */
		while ($source != $this->filterTags($source))
		{
			$source = $this->filterTags($source);
			$loopCounter ++;
		}
		return $source;
	}

	/**
	 * Internal method to strip a string of certain tags
	 *
	 * @access	protected
	 * @param	string	$source	Input string to be 'cleaned'
	 * @return	string	$source	'cleaned' version of input parameter
	 */
	function filterTags($source)
	{
		/*
		 * In the beginning we don't really have a tag, so everything is
		 * postTag
		 */
		$preTag		= null;
		$postTag	= $source;
		/*
		 * Is there a tag? If so it will certainly start with a '<'
		 */
		$tagOpen_start	= strpos($source, '<');

		while ($tagOpen_start !== false)
		{

			/*
			 * Get some information about the tag we are processing
			 */
			$preTag		   .= substr($postTag, 0, $tagOpen_start);
			$postTag		= substr($postTag, $tagOpen_start);
			$fromTagOpen	= substr($postTag, 1);
			$tagOpen_end	= strpos($fromTagOpen, '>');

			/*
			 * Let's catch any non-terminated tags and skip over them
			 */
			if ($tagOpen_end === false)
			{
				$postTag		= substr($postTag, $tagOpen_start +1);
				$tagOpen_start	= strpos($postTag, '<');
				continue;
			}

			/*
			 * Do we have a nested tag?
			 */
			$tagOpen_nested = strpos($fromTagOpen, '<');
			$tagOpen_nested_end	= strpos(substr($postTag, $tagOpen_end), '>');
			if (($tagOpen_nested !== false) && ($tagOpen_nested < $tagOpen_end))
			{
				$preTag		   .= substr($postTag, 0, ($tagOpen_nested +1));
				$postTag		= substr($postTag, ($tagOpen_nested +1));
				$tagOpen_start	= strpos($postTag, '<');
				continue;
			}


			/*
			 * Lets get some information about our tag and setup attribute pairs
			 */
			$tagOpen_nested	= (strpos($fromTagOpen, '<') + $tagOpen_start +1);
			$currentTag		= substr($fromTagOpen, 0, $tagOpen_end);
			$tagLength		= strlen($currentTag);
			$tagLeft		= $currentTag;
			$attrSet		= array ();
			$currentSpace	= strpos($tagLeft, ' ');

			/*
			 * Are we an open tag or a close tag?
			 */
			if (substr($currentTag, 0, 1) == "/")
			{
				// Close Tag
				$isCloseTag		= true;
				list ($tagName)	= explode(' ', $currentTag);
				$tagName		= substr($tagName, 1);
			} else
			{
				// Open Tag
				$isCloseTag		= false;
				list ($tagName)	= explode(' ', $currentTag);
			}

			/*
			 * Exclude all "non-regular" tagnames
			 * OR no tagname
			 * OR remove if xssauto is on and tag is blacklisted
			 */
			if ((!preg_match("/^[a-z][a-z0-9]*$/i", $tagName)) || (!$tagName) || ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto)))
			{
				$postTag		= substr($postTag, ($tagLength +2));
				$tagOpen_start	= strpos($postTag, '<');
				// Strip tag
				continue;
			}

			/*
			 * Time to grab any attributes from the tag... need this section in
			 * case attributes have spaces in the values.
			 */
			while ($currentSpace !== false)
			{
				$fromSpace		= substr($tagLeft, ($currentSpace +1));
				$nextSpace		= strpos($fromSpace, ' ');
				$openQuotes		= strpos($fromSpace, '"');
				$closeQuotes	= strpos(substr($fromSpace, ($openQuotes +1)), '"') + $openQuotes +1;

				/*
				 * Do we have an attribute to process? [check for equal sign]
				 */
				if (strpos($fromSpace, '=') !== false)
				{
					/*
					 * If the attribute value is wrapped in quotes we need to
					 * grab the substring from the closing quote, otherwise grab
					 * till the next space
					 */
					if (($openQuotes !== false) && (strpos(substr($fromSpace, ($openQuotes +1)), '"') !== false))
					{
						$attr = substr($fromSpace, 0, ($closeQuotes +1));
					} else
					{
						$attr = substr($fromSpace, 0, $nextSpace);
					}
				} else
				{
					/*
					 * No more equal signs so add any extra text in the tag into
					 * the attribute array [eg. checked]
					 */
					$attr = substr($fromSpace, 0, $nextSpace);
				}

				// Last Attribute Pair
				if (!$attr)
				{
					$attr = $fromSpace;
				}

				/*
				 * Add attribute pair to the attribute array
				 */
				$attrSet[] = $attr;

				/*
				 * Move search point and continue iteration
				 */
				$tagLeft		= substr($fromSpace, strlen($attr));
				$currentSpace	= strpos($tagLeft, ' ');
			}

			/*
			 * Is our tag in the user input array?
			 */
			$tagFound = in_array(strtolower($tagName), $this->tagsArray);

			/*
			 * If the tag is allowed lets append it to the output string
			 */
			if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod))
			{
				/*
				 * Reconstruct tag with allowed attributes
				 */
				if (!$isCloseTag)
				{
					// Open or Single tag
					$attrSet = $this->filterAttr($attrSet);
					$preTag .= '<'.$tagName;
					for ($i = 0; $i < count($attrSet); $i ++)
					{
						$preTag .= ' '.$attrSet[$i];
					}

					/*
					 * Reformat single tags to XHTML
					 */
					if (strpos($fromTagOpen, "</".$tagName))
					{
						$preTag .= '>';
					} else
					{
						$preTag .= ' />';
					}
				} else
				{
					// Closing Tag
					$preTag .= '</'.$tagName.'>';
				}
			}

			/*
			 * Find next tag's start and continue iteration
			 */
			$postTag		= substr($postTag, ($tagLength +2));
			$tagOpen_start	= strpos($postTag, '<');
		}

		/*
		 * Append any code after the end of tags and return
		 */
		if ($postTag != '<')
		{
			$preTag .= $postTag;
		}
		return $preTag;
	}

	/**
	 * Internal method to strip a tag of certain attributes
	 *
	 * @access	protected
	 * @param	array	$attrSet	Array of attribute pairs to filter
	 * @return	array	$newSet		Filtered array of attribute pairs
	 */
	function filterAttr($attrSet)
	{
		
		/*
		 * Initialize variables
		 */
		$newSet = array ();

		/*
		 * Iterate through attribute pairs
		 */
		for ($i = 0; $i < count($attrSet); $i ++)
		{
			/*
			 * Skip blank spaces
			 */
			if (!$attrSet[$i])
			{
				continue;
			}

			/*
			 * Split into name/value pairs
			 */
			$attrSubSet = explode('=', trim($attrSet[$i]), 2);
			list ($attrSubSet[0]) = explode(' ', $attrSubSet[0]);

			/*
			 * Remove all "non-regular" attribute names
			 * AND blacklisted attributes
			 */
			if ((!eregi("^[a-z]*$", $attrSubSet[0])) || (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist)) || (substr($attrSubSet[0], 0, 2) == 'on'))))
			{
				continue;
			}

			/*
			 * XSS attribute value filtering
			 */
			if ($attrSubSet[1])
			{
				// strips unicode, hex, etc
				$attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]);
				// strip normal newline within attr value
				$attrSubSet[1] = preg_replace('/\s+/', '', $attrSubSet[1]);
				// strip double quotes
				$attrSubSet[1] = str_replace('"', '', $attrSubSet[1]);
				// [requested feature] convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr value)
				if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'"))
				{
					$attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2));
				}
				// strip slashes
				$attrSubSet[1] = stripslashes($attrSubSet[1]);
			}

			/*
			 * Autostrip script tags
			 */
			if (InputFilter::badAttributeValue($attrSubSet))
			{
				continue;
			}

			/*
			 * Is our attribute in the user input array?
			 */
			$attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray);

			/*
			 * If the tag is allowed lets keep it
			 */
			if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod))
			{
				/*
				 * Does the attribute have a value?
				 */
				if ($attrSubSet[1])
				{
					$newSet[] = $attrSubSet[0].'="'.$attrSubSet[1].'"';
				}
				elseif ($attrSubSet[1] == "0")
				{
					/*
					 * Special Case
					 * Is the value 0?
					 */
					$newSet[] = $attrSubSet[0].'="0"';
				} else
				{
					$newSet[] = $attrSubSet[0].'="'.$attrSubSet[0].'"';
				}
			}
		}
		return $newSet;
		
	}

	/**
	 * Function to determine if contents of an attribute is safe
	 *
	 * @access	protected
	 * @param	array	$attrSubSet	A 2 element array for attributes name,value
	 * @return	boolean True if bad code is detected
	 */
	function badAttributeValue($attrSubSet)
	{
		$attrSubSet[0] = strtolower($attrSubSet[0]);
		$attrSubSet[1] = strtolower($attrSubSet[1]);
		return (((strpos($attrSubSet[1], 'expression') !== false) && ($attrSubSet[0]) == 'style') || (strpos($attrSubSet[1], 'javascript:') !== false) || (strpos($attrSubSet[1], 'behaviour:') !== false) || (strpos($attrSubSet[1], 'vbscript:') !== false) || (strpos($attrSubSet[1], 'mocha:') !== false) || (strpos($attrSubSet[1], 'livescript:') !== false));
	}

	/**
	 * Try to convert to plaintext
	 *
	 * @access	protected
	 * @param	string	$source
	 * @return	string	Plaintext string
	 */
	function decode($source)
	{
		// url decode
		$source = html_entity_decode($source, ENT_QUOTES);
		// convert decimal
		$source = preg_replace('/&#(\d+);/me', "chr(\\1)", $source); // decimal notation
		// convert hex
		$source = preg_replace('/&#x([a-f0-9]+);/mei', "chr(0x\\1)", $source); // hex notation
		return $source;
	}

	

	
}
?>
Пример использования класса:
PHP:
<?php 
			
			$var		 = $_POST['var']; 		
$safeHtmlFilter = new InputFilter(null, null, 1, 1);
			$var = $safeHtmlFilter->process($var);
			//$var = nl2br($var);
?>

Проверка при загрузке файлов по http

Есть еще один вопрос, связанный с безопасностью, который стоит затронуть на мой взгляд. Это загрузка файлов по http.

Желательно после загрузки проверять файлы с помощью функции is_uploaded_file
bool is_uploaded_file ( string $filename )

Возвращает TRUE, если файл filename был загружен при помощи HTTP POST. Это полезно, чтобы убедиться в том, что злонамеренный пользователь не пытается обмануть скрипт так, чтобы он работал с файлами, с которыми работать не должен -- к примеру, /etc/passwd.

А так же проверять расширение загружаемого файла, чтобы удаленный пользователь не мог загрузить допустим файл с расширением php к вам на сервер, а потом выполнить его код.

Вот пример как можно сделать такую проверку:
PHP:
<?php 

$valid		= array('gif','png','jpg'); //через запятую указываем правильные расширения файлов, которые нужно “пропустить” 

		

function _check_exts($file,$valid){
		
		
	    if(is_string($valid) && !empty($valid)){
			
		$valid		= array($valid);
		}
		
		if(!is_array($valid)){
			return false;
		}
		
		
		$valid		= array_map('trim',$valid);
		$valid		= array_map('strtolower',$valid);
		$valid		= array_unique($valid);
		
		if(empty($file['tmp_name']) || empty($file['size'])) {
			$file	= null;
					unset($file);
			
		};
		
		$is_upload	= is_uploaded_file($file['tmp_name']);
		
		
		if(!$is_upload){
			$file	= null;
			
					unset($file);
			
			return false;
		}
		
					unset($is_upload);
		
		
		$ext		= false;
		$ext		= getExtByName($file['name']);
		
		
		
		if(!$ext){
					
					unset($file);
			
			return false;
		}
					
		
		
		
		if(!in_array($ext,$valid)){
			$file	= null;
			
					unset($file);
				
				return false;
		}
					unset($valid);
			
		return true;
	}
	
	
	function getExtByName($name){
		$name		= strtolower($name);
		$pos		= 0;
		$ext		= '';
		
		if(($pos    = strpos($name,'.')) !== false){
			
			$arr	= array();
			$arr	= explode('.',$name);
			$sizeof	= sizeof($arr);
			$ext	= strtolower($arr[$sizeof-1]);
		}
		return $ext?$ext:false;
	}

	$is_valid				= _check_exts($_FILES['img']);
	if(!$is_valid){
		unset($_FILES['img']);
	}
?>

Параметризированные запросы, placeholders.

Есть еще один способ отправлять запросы в БД, называемый "подготовленными выражениями" (prepared statements).

Суть его заключается в том, что подготавливается шаблон запроса, со специальными маркерами, на место которых будут подставлены динамические компоненты. Пример такого шаблона:
SELECT * FROM table WHERE name=?

Знак вопроса здесь - это тот самый маркер. По-другому он называетсй плейсхолдером (placeholder). Весь секрет в том, что данные на его место подставляет специальная функция, которая "привязывает" переменную к запросу.
Вот как выглядит код в таком случае:

PHP:
$mysqli = mysqli_connect("localhost","test","","world");
$stmt = $mysqli->prepare("SELECT District FROM City WHERE Name=?");
$stmt->bind_param("s", $city);
$stmt->execute();

Остановимся подробнее на коде этого примера.

$stmt = $mysqli->prepare("SELECT District FROM City WHERE Name=?");
$stmt->bind_param("s", $city);

Данная подстановка называется подстановкой на входе: значение переменной $city подставляется в текст запроса на место маркера ?.

mysqli_stmt_bind_param()
$stmt->bind_param ()
Выполняет подстановку значений переменных в запрос.

Аргументы:
Дескриптор запроса (только для функции)
Строка определяющая типы подстанавливаемых переменных (s - string, i-number,d-double,b-blob)
Список переменных


В четвертой строке выполняется собственно запрос.

Запрос и данные идут по отдельности, исключая возможность какой-либо ошибки или злонамеренной манипуляции.

См. документацию по библиотекам mysqli и PDO, реализующим данный принцип.

Так же можно использовать библиотеку DbSimple Дмитрия Котерова или PEAR:: DB. Основное отличие этих двух состоит в том, что они реализуют механизм подготовленных выражений только внешне. А внутри работают по-старинке - составляя запрос и отправляя его в базу, беря на себя работу по обработке переменных. А PDO и mysqli работают, как было описано выше - то есть, шаблон запроса и данные уходят в базу по отдельности.

Пример применения функции Sprintf для имитации параметризированных запросов от Для просмотра ссылки Войди или Зарегистрируйся.
PHP:
<?php

$id = 123;

echo $sql = 'SELECT * FROM table WHERE id = ' . $id;
echo '<br />';
echo $sql = sprintf('SELECT * FROM table WHERE id = %d', $id);

echo '<br />';
echo '<br />';

$id = "' '; DELETE FROM customers";

echo $sql = 'SELECT * FROM table WHERE id = ' . $id;
echo '<br />';
echo $sql = sprintf('SELECT * FROM table WHERE id = %d', $id);

?>

Функция:

string sprintf ( string $format [, mixed $args ] )

Возвращает строку, созданную с использованием строки формата format .

Описатель типа, определяющий, как трактовать тип данных аргумента. Допустимые типы:

  • % - символ процента. Аргумент не используется.
  • b - аргумент трактуется как целое и выводится в виде двоичного числа.
  • c - аргумент трактуется как целое и выводится в виде символа с соответствующим кодом ASCII.
  • d - аргумент трактуется как целое и выводится в виде десятичного числа со знаком.
  • e - аргумент трактуется как float и выводится в научной нотации (например 1.2e+2).
  • u - аргумент трактуется как целое и выводится в виде десятичного числа без знака.
  • f - аргумент трактуется как float и выводится в виде десятичного числа с плавающей точкой.
  • o - аргумент трактуется как целое и выводится в виде восьмеричного числа.
  • s - аргумент трактуется как строка.
  • x - аргумент трактуется как целое и выводится в виде шестнадцатиричного числа (в нижнем регистре букв).
  • X - аргумент трактуется как целое и выводится в виде шестнадцатиричного числа (в верхнем регистре букв).

В нашем примере описатель типа %d - целое число, которое будет заменено на величину $id перед запросом к СУБД и приведено к целому типу чисел.

Пример использования си подобных операторов для явного приведения типов при подготовке строки в виде конкатенции параметров:

PHP является слабо типизированным языком программирования. Но в нем присутствую операторы явного приведения типов.
Результатом будет:

  • (int)(integer) - целое число
  • (float),(real),(double) - вещественное число
  • (string) - строка
  • (bool),(boolean) - Логическое значение
  • (array) - Массив
  • (object) - Объект


Операнд записывается справа от оператора.

Пример подготовки запроса с использованием операторов приведения типов и конкатенции строки запроса и параметров:

PHP:
<?php

$id = $_GET['id'];

$sql = 'SELECT * FROM tbl WHERE id='.(int)$id;

?>
Здесь тип присоединяемого к запросу параметра ай ди приводится к целому числу. А потом участвует в конкатенции запроса.

PS: весь приведенный код тестировался и работает (видоизмененно) в виде классов в некоторых моих проектах. Об ошибках, описках, дополнениях просьба писать ниже.
 
Для новичков сгодится, а как статья слабенькая - много неточностей.
 
К примеру каких?
Пожалуйста, по пунктам. О неточностях.
Не нужно быть голословным. И высказывать: "статья слабенькая".
В общем то эта статься на новичков и расчитана. Для профи это прописные истины.
На которые раз за разом как на грабли натыкаешься в очередном приложении.
Особенно на xss, о том как вскрываются сайты javascript ом, вообще молчу. Куда не ткнись - этого полно.
Я вынес за пределы этой статьи такие вопросы как:
- Безопасность на стороне клиента, чем может помочь Javascript
- Обзор фреймворков и предоставляемые ими средства для построения безопасных приложений
- MOD_SECURITY
- Построение фильтров входных данных для пользовательских данных
Так как это сугубо личное, и там еще не на одну такую статью информации наберется.
По поводу:
"статься слабенькая" - дополни. Человеческим понятным языком с работающими примерами. Исправь. Если что не так. Люблю конструктивную критику на самом деле.
А по js все таки в ближайшие дни напишу.
 
Язык: HTML + PHP
PHP код:
<!—- Заголовок -->
<?php
include ($page);
?>
<!—- Завершающая часть -->
Соответственно, работа идет со ссылками типа Для просмотра ссылки Войди или Зарегистрируйся. Самое безобидное, что можно сделать – это просмотреть информацию о PHP (и не только:(

Листинг
Язык: PHP<?php
phpinfo();
?>
Создав такой файл у себя на сервере, просто включаем его в запрос вместо about.php – и видим всю информацию на экране. Таким образом, мы внедрили произвольный код в скрипт на сервере и получили необходимую информацию. Но это только цветочки, ведь можно при помощи PHP сделать все, что нашей душе угодно.
Хрен ты у меня на сервере создашь файл about.php или проведёшь удалённый инклуд.
Так о каком "мы внедрили произвольный код в скрипт на сервере" может вестись речь?

Можешь спорить со мной до усрачки, можешь заявлять о "голословных высказываниях", но в таком стиле как написаны все твои примеры (имеются в виду объекты для атаки) не пишет ни один нормальный программер - только новички/самоучки и полные ламо.
 
А теперь по полочкам:
- были наипростейшие примеры. очень наипростейшие. так как многие новички вообще не слышали sql, xss и т.п. injection.
Теперь разуй глаза и найди в моём посте слова "Для новичков сгодится" :p
Дай адрес на свой севрер - я протестирую его на безопасность. Там, где работают действительно написанные тобою сайты, скрипты. Забью на все, выделю неделю и получишь список уязвимостей. В приват. Если нахожу - цена вопроса 500-600 евро. За дефейс. Выкачку. Согласен? Предопалата.
На такой мякине разводи своих дружбанов :D
 
мне конечно пох, но нахера говорить что ты сможешь сломать все что угодно ?
Black#FFFFFF, понимаешь, нормальные прогеры делают проверку входящих данных - и дефейс в таком варианте сделать невозможно.

да, можно развести философию и прийти к тому что можно сломать все что угодго - например воздействовав молотком. но это философия.
 
Но твоя боязнь за твои высококачественные высоконадежные проекты естественно понятна.
Это не боязнь - это нежелание разводить тупые бесмысленные споры.
Теперь по теме - речь о безопасном программировании, каким боком здесь сисадмин?

Сделай доброе дело - собери тогда здесь статейку по настоящим уязвимостям.
Как-нибудь сделаю.

Или только как обычно набираешь посты путем создания несущественных комментариев?
Ага, именно так :D
Я написал, что статья для новичков, ты с этим согласен - в чём проблема?
Или ты ждал, чтоб тебя в зад лизнули? Ну тогда надо было в детский сад с таким материалом.
 
Не надо путать тёплое с мягким - программист не в ответе, что у некоторых админов руки из жопы, и наоборот.
Или если слили файлы по вине админа, то виноват программист?

Тема данного топика программирование, а не комплексная безопасность.
 
Посмотри внимательно в название темы.
Я не говорю что могу сломать что угодно. И по поводу нормальных программистов вэб сейчас в эпохе дикого запада. Почему столько уязвимостей особенно xss если все программисты нормальные? Почему существуют десятки сайтов по безопасности отслеживающих дыры в тех или иных движках?
Вот из за этого и была создана эта тема. Чтобы даже те, кто начинает, прочитали. Задумались.
По поводу взлома приведу хорошую цитату одного из наших университетских преподавателей:
- Запомните. Идеальная безопасность - это когда Ваш компьютер закрыт в подвале с автономным источником питания. При этом вход и выход в подвал наглухо заварены. У компьютера отсутствют любые устройства ввода-вывода и передачи информации вовне.
Взламывается все. Оптимальным условием защиты является цена за время приблизительно равна = времени
потраченному на взлом.
ты не в названии темы сказал про дефейснуть/сломать, не уходи от слов.
никто не говорит что все програмисты нормальные, говорю что нормальные проверяют входящие данные.
гы, еще цитата твоего препода что все взламывается. зачем быть такими гордыми. тогда взломайте наш мир, чтобы 2*2=5 а не 4. передай своему преподу что математика и логика не взламывается. математика и логика была есть и будет всегда, и это TRUE :read:
 
Black#FFFFFF написал(а):
Очень приблизительное предположение основанное на догадках и домыслах трехмерного пространства. Пора выходить за рамки.
Вот давай выйдем. Ломаем аксиому на "0 делить нельзя":
1-1=1-1?
2+2=4?
2+2/(1-1)=4/(1-1)?
(2+2)/4=(1-1)/(1-1)
1=0?:)
А как же неэвклидова геометрия.
А как же теория комплексных чисел?
Математика, логика, все противоречит само себе.


у тебя это получилось, ты взломал мир :ak:
ну теперь я сдаюсь :ai:
 
Статус
В этой теме нельзя размещать новые ответы.
Назад
Сверху