Материал предназначен в основном для начинающих веб-программистов.
Введение.
Часто ко мне обращаются клиенты, у которых установлены самописные CMS или модули, написанные начинающими веб-программистами, которые не понимают, что нужно для защиты данных и зачастую копируют функции фильтрации, не задумываясь о том как они работают и что именно нужно с ними делать.Здесь я постараюсь описать как можно подробнее частые ошибки при фильтрации данных в PHP скрипте и дать простые советы как правильно выполнить фильтрацию данных.
В сети много статей по поводу фильтрации данных, но они как правильно не полные и без подробные примеров.
Разбор полетов.
Фильтрация. Ошибка №1
Для числовых переменных используется такая проверка:$number = $_GET["input_number"]; if (intval($number)) { ... выполняем SQL запрос... }
Почему она приведет к SQL инъекции? Дело в том, что пользователь может указать в переменной input_number значение:
1"+UNION+SELECT
В таком случаи проверка будет успешно пройдена, т.к. функция intval получает целочисленное значение переменной, т.е. 1, но в самой переменной $number ничего не изменилось, поэтому весь вредоносный код будет передан в SQL запрос.
Правильная фильтрация:
$number = intval($_GET["input_number"]); if ($number) { ... выполняем SQL запрос... }
Конечно, условие может меняться, например если вам нужно получить только определенный диапазон:
if ($number >= 32 AND $number <= 65)
Если вы используете чекбоксы или мультиселекты с числовыми значениями, выполните такую проверку:
$checkbox_arr = array_map("intval", $_POST["checkbox"]);
array_map
Так же встречаю фильтрацию в виде:
$number = htmlspecialchars(intval($_GET["input_number"]));
htmlspecialchars
Или:
$number = mysql_escape_string(intval($_GET["input_number"]));
mysql_escape_string
Ничего кроме улыбки это не может вызвать:)
Фильтрация. Ошибка №2.
Для стринг-переменных используется такая фильтрация:$input_text = addslashes($_GET["input_text"]);
Функция addslashes экранирует спец. символы, но она не учитывает кодировку БД и возможен обход фильтрации. Не стану копировать текст автора, который описал данную уязвимость и дам просто ссылку Chris Shiflett (перевод можно поискать в рунете).
Используйте функцию mysql_escape_string или mysql_real_escape_string , пример:
$input_text = mysql_escape_string($_GET["input_text"]);
Если вы не предполагаете вхождение html тегов, то лучше всего сделать такую фильтрацию:
$input_text = strip_tags($_GET["input_text"]);
$input_text = htmlspecialchars($input_text);
$input_text = mysql_escape_string($input_text);
strip_tags - убирает html теги.
htmlspecialchars - преобразует спец. символы в html сущности.
Так вы защитите себя от XSS атаки, помимо SQL инъекции.
Если же вам нужны html теги, но только как для вывода исходного кода, то достаточно использовать:
$input_text = htmlspecialchars($_GET["input_text"]);
$input_text = mysql_escape_string($input_text);
Если вам важно, чтобы значение переменной не было пустой, то используйте функцию trim , пример:
$input_text = trim($_GET["input_text"]);
$input_text = htmlspecialchars($input_text);
$input_text = mysql_escape_string($input_text);
Фильтрация. Ошибка №3.
Она касается поиска в БД.Для поиска по числам используйте фильтрацию, описанную в первой ошибке.
Для поиска по тексту используйте фильтрацию, описанную во второй ошибке, но с оговорками.
Для того, чтобы пользователь не смог выполнить логическую ошибку, нужно удалять или экранировать спец. символы SQL.
Пример без доп. обработки строки:
$input_text = htmlspecialchars($_GET["input_text"]); // Поиск: "%" $input_text = mysql_escape_string($input_text);
На выходе у нас получится запрос вида:
... WHERE text_row LIKE "%".$input_text."%" ... // WHERE text_row LIKE "%%%"
Это значительно увеличит нагрузку на базу.
В своём скрипте я использую функцию, которая удаляет нежелательные мне символы из поиска:
function strip_data($text) { $quotes = array ("\x27", "\x22", "\x60", "\t", "\n", "\r", "*", "%", "<", ">", "?", "!"); $goodquotes = array ("-", "+", "#"); $repquotes = array ("\-", "\+", "\#"); $text = trim(strip_tags($text)); $text = str_replace($quotes, "", $text); $text = str_replace($goodquotes, $repquotes, $text); $text = ereg_replace(" +", " ", $text); return $text; }
Конечно, не все из выше перечисленных символов представляют опасность, но в моём случаи они не нужны, поэтому выполняю поиск и замену.
Пример использования фильтрации:
$input_text = strip_data($_GET["input_text"]); $input_text = htmlspecialchars($input_text); $input_text = mysql_escape_string($input_text);
Также советую сделать ограничение по количеству символов в поиске, хотя бы не меньше 3-х, т.к. если у вас будет большое количество записей в базе, то поиск по 1-2 символам будет значительно увеличивать нагрузку на БД.
Фильтрация. Ошибка №4.
Не фильтруются значения в переменной $_COOKIE . Некоторые думаю, что раз эту переменную нельзя передать через форму, то это гарантия безопасности.Данную переменную очень легко подделать любым браузером, отредактировав куки сайта.
Например, в одной известной CMS была проверка, используемого шаблона сайта:
if (@is_dir (MAIN_DIR . "/template/" . $_COOKIE["skin"])){ $config["skin"] = $_COOKIE["skin"]; } $tpl->dir = MAIN_DIR . "/template/" . $config["skin"];
В данном случаи можно подменить значение переменной $_COOKIE["skin"] и вызвать ошибку, в результате которой вы увидите абсолютный путь до папки сайта.
Если вы используете значение куков для сохранения в базу, то используйте одну из выше описанных фильтраций, тоже касается и переменной $_SERVER .
Фильтрация. Ошибка №5.
Включена директива register_globals . Обязательно выключите её, если она включена.В некоторых ситуациях можно передать значение переменной, которая не должна была передаваться, например, если на сайте есть группы, то группе 2 переменная $group должна быть пустой или равняться 0, но достаточно подделать форму, добавив код:
В PHP скрипте переменная $group будет равна 5, если в скрипте она не была объявлена со значением по умолчанию.
Фильтрация. Ошибка №6.
Проверяйте загружаемые файлы.Выполняйте проверку по следующим пунктам:
- Расширение файла. Желательно запретить загрузку файлов с расширениями: php, php3, php4, php5 и т.п.
- Загружен ли файл на сервер move_uploaded_file
- Размер файла
Проверка. Ошибка №1.
Сталкивался со случаями, когда для AJAX запроса (например: повышение репутации) передавалось имя пользователя или его ID (кому повышается репутация), но в самом PHP не было проверки на существование такого пользователя.Например:
$user_id = intval($_REQUEST["user_id"]); ... INSERT INTO REPLOG SET uid = "{$user_id}", plus = "1" ... ... UPDATE Users SET reputation = reputation+1 WHERE user_id = "{$user_id}" ...
Получается мы создаем запись в базе, которая совершенно бесполезна нам.
Проверка. Ошибка №2.
При выполнении различного рода действий (добавление, редактирование, удаление) с данными не забывайте проверять права пользователя на доступ к данной функции и дополнительные возможности (использование html тегов или возможность опубликовать материал без проверки).Давно исправлял в одном модуле форума подобную ошибку, когда любой пользователь мог отредактировать сообщение администрации.
Проверка. Ошибка №3.
При использовании нескольких php файлов сделайте простую проверку.В файле index.php (или в любом другом главном файле) напишите такую строчку перед подключением других php файлов:
define ("READFILE", true);
В начале других php файлов напишите:
if (! defined ("READFILE")) { exit ("Error, wrong way to file.
Go to main."); }
Так вы ограничите доступ к файлам.
Проверка. Ошибка №4.
Используйте хеши для пользователей. Это поможет предотвратить вызов той или иной функции путём XSS.Пример составления хеша для пользователей:
$secret_key = md5(strtolower("http://site.ru/" . $member["name"] . sha1($password) . date("Ymd"))); // $secret_key - это наш хеш
Далее во все важные формы подставляйте инпут со значением текущего хеша пользователя:
Во время выполнения скрипта осуществляйте проверку:
if ($_POST["secret_key"] !== $secret_key) { exit ("Error: secret_key!"); }
Проверка. Ошибка №5.
При выводе SQL ошибок сделайте простое ограничение к доступу информации. Например задайте пароль для GET переменной:if ($_GET["passsql"] == "password") { ... вывод SQL ошибки... } else { ... Просто информация об ошибке, без подробностей... }
Это позволит скрыть от хакера информацию, которая может ему помочь во взломе сайта.
Проверка. Ошибка №5.
Старайтесь не подключать файлы, получая имена файлов извне.Например:
if (isset($_GET["file_name"])) { include $_GET["file_name"] .".php"; }
Используйте переключатель
null function (11)
У меня есть (или нет) переменная $_GET["myvar"] исходящая из моей строки запроса, и я хочу проверить, существует ли эта переменная, а также если значение соответствует чему-то внутри моего оператора if:
То, что я делаю и думаю, это не лучший способ сделать:
if(isset($_GET["myvar"]) && $_GET["myvar"] == "something") : сделать что-то
Это простой случай, но представьте себе, что нужно сравнить многие из этих переменных $myvar .
Answers
Это похоже на принятый ответ, но вместо этого использует in_array . Я предпочитаю использовать empty() в этой ситуации. Я также предлагаю использовать новое объявление строкового массива, которое доступно в PHP 5.4.0 +.
$allowed = ["something","nothing"]; if(!empty($_GET["myvar"]) && in_array($_GET["myvar"],$allowed)){..}
Вот функция для проверки сразу нескольких значений.
$arrKeys = array_keys($_GET); $allowed = ["something","nothing"]; function checkGet($arrKeys,$allowed) { foreach($arrKeys as $key) { if(in_array($_GET[$key],$allowed)) { $values[$key]; } } return $values; }
Я использую всю свою собственную полезную функцию exst (), которая автоматически объявляет переменные.
$element1 = exst($arr["key1"]); $val2 = exst($_POST["key2"], "novalue"); /** * Function exst() - Checks if the variable has been set * (copy/paste it in any place of your code) * * If the variable is set and not empty returns the variable (no transformation) * If the variable is not set or empty, returns the $default value * * @param mixed $var * @param mixed $default * * @return mixed */ function exst(& $var, $default = "") { $t = ""; if (!isset($var) || !$var) { if (isset($default) && $default != "") $t = $default; } else { $t = $var; } if (is_string($t)) $t = trim($t); return $t; }
Ну, вы можете обойтись, только if($_GET["myvar"] == "something") поскольку это условие предполагает, что переменная также существует. Если это не так, выражение также приведет к false .
Я думаю, что это нормально делать в условных выражениях, как указано выше. На самом деле никакого вреда.
Мой вопрос: существует ли способ сделать это без объявления переменной дважды?
Нет, нет способа сделать это правильно, не выполняя две проверки. Я тоже это ненавижу.
Один из способов обойти это - это импортировать все соответствующие переменные GET в одну центральную точку в массив или объект определенного типа (большинство из них MVC делают это автоматически) и устанавливают все свойства, которые необходимы позже. (Вместо доступа к переменным запроса через код.)
If (isset($_GET["myvar"]) == "something")
Благодаря Mellowsoon и Pekka, я сделал некоторые исследования здесь и придумал это:
- Проверяйте и объявляйте каждую переменную как null (если это так) перед началом использования (как рекомендовано):
* ok, это просто, но отлично работает, вы можете начать использовать переменную всюду после этой строки
- Использование массива для всех случаев:
* после этого вы можете использовать свои переменные (var1, var2, var3 ... и т. д.),
PS: функция, получающая объект JSON, должна быть лучше (или простая строка с разделителем для взрыва / взрыва);
Лучшие подходы приветствуются:)
ОБНОВИТЬ:
Используйте $ _REQUEST вместо $ _GET, таким образом вы покрываете переменные $ _GET и $ _POST.
Isset($_REQUEST[$key]) ? $_REQUEST[$key] =0:0;
Решение, которое я нашел от игры, это сделать:
If($x=&$_GET["myvar"] == "something") { // do stuff with $x }
Как подскажите, вы можете рассмотреть этот подход:
Required = array("myvar" => "defaultValue1", "foo" => "value2", "bar" => "value3", "baz" => "value4"); $missing = array_diff($required, array_keys($_GET)); foreach($missing as $key => $default) { $_GET[$key] = $default ; }
Вы устанавливаете значения по умолчанию и устанавливаете не получаемые параметры в значение по умолчанию:)
К сожалению, это единственный способ сделать это. Но есть подходы к работе с большими массивами. Например, что-то вроде этого:
$required = array("myvar", "foo", "bar", "baz"); $missing = array_diff($required, array_keys($_GET));
Теперь переменная $ missing содержит список значений, которые требуются, но отсутствуют в массиве $ _GET. Вы можете использовать $ missing array для отображения сообщения посетителю.
Или вы можете использовать что-то вроде этого:
$required = array("myvar", "foo", "bar", "baz"); $missing = array_diff($required, array_keys($_GET)); foreach($missing as $m) { $_GET[$m] = null; }
Теперь каждый требуемый элемент имеет по умолчанию значение по умолчанию. Теперь вы можете использовать if ($ _ GET ["myvar"] == "something"), не беспокоясь о том, что ключ не установлен.
Обновить
Другим способом очистки кода будет использование функции, которая проверяет, установлено ли значение.
Function getValue($key) { if (!isset($_GET[$key])) { return false; } return $_GET[$key]; } if (getValue("myvar") == "something") { // Do something }
почему бы не создать функцию для этого, преобразовать переменную, которую вы хотите проверить, в реальную переменную, например.
Function _FX($name) { if (isset($$name)) return $$name; else return null; }
то вы делаете _FX("param") == "123" , просто мысль
Я нашел (намного) лучший код, чтобы сделать это, если вы хотите проверить что-нибудь в .
If [[ $1 = "" ]] then echo "$1 is blank" else echo "$1 is filled up" fi
Почему все это? Все в существует в Bash, но по умолчанию оно пустое, поэтому test -z и test -n не могут вам помочь.
If [ ${#1} = 0 ] then echo "$1 is blank" else echo "$1 is filled up" fi
Можно проверить, существует ли данная переменная, (то есть инициализирована или она). Для этого используется функция:
Isset(переменная);
Если переменной в данный момент не существует (нигде ранее ей не присваивалось значение или она была удалена функцией unset () ), то функцияisset () возвращаетfalse , в противном случае –true :
$x = 5;
if (isset($x))
echo ‘< BR >Переменная $ x существует, ‘, “ее значение равно $ x < BR >”;
На экране появится:
Переменная $ x существует, ее значение равно 5
Важно помнить, что мы не можем использовать в программе неинициализированную переменную – это породит предупреждение со стороны интерпретатора PHP .
Чтобы выяснить, является ли значение переменнойпустым , используется функция:
empty( переменная);
Если значение переменной равно нулю ,“0”, NULL , пустой строке (“” ),false, переменная не объявлена или являетсяпустым массивом , то эта функция возвращаетtrue , в противном случае –false .
Чтобы проверитьтип переменной, используются функции:
Is_string(переменная);
is _ int (переменная);
is _ float (переменная);
is _ null (переменная);
is _ array (переменная);
is _ numeric (переменная); - если переменная является числовой (integer , float ) или строкой, содержащей только числа.
Эти функции возвращают true , если переменная имеет указанный тип.
Вывод данных
Бесформатный вывод
Бесформатный вывод строк или значений переменных осуществляется функцией:
echo список переменных;
echo строка;
где список переменных – имена выводимых переменных через запятые.
Если мы работаем с веб-браузером, то эта функция направляет вывод в клиентскую часть браузера (в его окно).
Как это уже было сказано, если в строке, заключенной в двойные кавычки, встречаются имена переменных, то на экран вместо этих имен выводятся соответствующие им значения. Более того, если в такой строке встречаются теги HTML (дескрипторы, заключенные в угловые скобки), то браузер отображает этотHTML -код так, как он должен это делать при интерпретацииHTML -документа:
$year = 2012;
$message = “ Желаю всем счастья !”;
echo
“
Мои
поздравления
!”;
echo
“
Наступил
$year
год
!
$message
”;
?>
На экран будет выведен заголовок уровня H 3 и последующее приветствие, причем слово “счастья!” будет выведено полужирным курсивом:
Мои поздравления!
Наступил 2012 год! Желаю всем счастья!
Так можно создавать динамические сайты.
Форматированный вывод
Форматированный вывод позволяет представлять выводимые числа в различных системах счисления, а в десятичной системе – в различных видах (форматах ). Он похож на форматированный вывод вСи и осуществляется функциями:
printf (“формат”, список вывода);
sprintf (“формат”, список вывода);
Первая функция выводит в окно браузера отформатированные данные и возвращает их количество.
Вторая функция только форматирует выводимые данные, но не выводит их.
Формат – это последовательность описателей преобразований для выводимых значений.
Описатель преобразований для каждого значения имеет вид:
% ЗаполнительВыравниваниеДлина.ТочностьТип
- Заполнитель – это символ, который будет использоваться для дополнения результата преобразования до заданнойдлины (по умолчанию –пробел ); если это другой символ, то перед ним ставится одинарная кавычка (апостроф ),
- Выравнивание – по умолчанию – поправому краю поля вывода; если стоит минус (- ), то полевому ,
- Длина – ширина поля вывода - количество знакомест, отводимых для вывода этого значения. Если выводимое значение содержит меньше знакомест, чем заданнаядлина , то оставшееся пространство будет заполненопробелами или символами заполнения,
- Точность – количество десятичных разрядов в дробной части числа,
- Тип – тип выводимого значения:
b –двоичное ,
с –символ ,
d –целое в десятичной системе счисления,
е –вещественное в экспоненциальной форме (с плавающей запятой),
f –вещественное в форме с фиксированной запятой,
s –строка ,
о –целое в восьмеричной системе счисления,
x –целое в 16-ричной системе счисления.
Пример:
php
$ zarp _1 = 6543.21;
$ zarp _2 = 45321.67;
$ fam _1 = "Балаганов";
$ fam _2 = "Бендер";
printf ("< H 1>Платежная ведомость h 1>");
printf("%".-12s%".10.2f руб.", $fam_1, $zarp_1);
echo
"
";
printf("%".-12s%".10.2f руб.", $fam_2, $zarp_2);
echo
"
";
?>
В качестве заполнителя была выбрана точка (‘. ) . Фамилии выравниваются по левому краю (- ) в поле шириной12 символов. Числа представляются в форме с фиксированной запятой в поле шириной10 символов и с точностью2 знака после запятой, с выравниванием по правому краю.