Безопасное программирование на PHP

Автор: content Понедельник, Апрель 9th, 2012 Нет комментариев

Рубрика: Язык PHP

Безопасность web-приложений имеет большое значение и занимает одно из первых мест в построение надежного и качественного сайта. Рано или поздно сайт подвергнется атаке, пренебрежение безопасностью при написании Web-приложений приведёт к тому, что в лучшем случае вы увидите на главной станице сайта надпись «взломан», а в худшем потеряете важную информацию.

В этой статье мы поделимся с вами теми приемами, которыми используем при построении сайтов. Будут рассмотрены методы защиты, позволяющие предотвратить или, по крайней мере, снизит подверженность приложения к XSS-атакам и SQL-инъекциям.

Межсайтовый скрипинг (Cross Site Scripting — XSS) позволяет злоумышленнику включать свой HTML код в вашу станицу. Наиболее уязвимы для такого вида атак являются гостевые книги и форумы, где происходит динамическое формирование страниц. Возможности кода, который злоумышленник может вставить в код сайта практически не ограничены, например, вставка тега <img> со ссылкой на скрипт, который расположен на подконтрольном сайте позволяет собирать различную информации (например, cookie). Суть атаки — выйти за пределы HTML тега, через специальные символы, и далее внедрять свой код. Выход из тега чаще происходит с использованием следующих символов — ‘ (одинарная кавычка), » (двойная кавычка), ´ (обратная кавычка), > (знак «больше»). Защита от этого вида атак сводится к фильтрованию данных отосланных пользователем.

Замечание

Подробнее с XSS-атаками можно познакомиться по сслыкам:
http://www.bugtraq.ru/library/www/xssanatomy.html
http://www.bugtraq.ru/library/www/advancedxss.html

Рассмотрим код уязвимой станицы

<input name="username" value="<? echo $_GET['username'] ?>">

Злоумышленнику достаточно сформировать URL следующего вида:

http://www.server.com/index.php?username="><script>alert(document.cookie)</script>

и страница будет уже содержать следующий код:

<input name="username" value=""><script>alert(document.cookie)</script>">

Данный код не причиняет вреда, а лишь демонстрирует уязвимость XSS, отображая ваши cookie. Следует помнить, что код может быть другим, более опасным и, будучи оставленным, например, в сообщении гостевой книги будет запускаться на машине каждого из посетителей гостевой книги.

Замечание

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

Замечание

Синтаксис регулярных выражений является достаточно сложным и его изучение требует серьёзных усилий. Наилучшим руководством по регулярным выражением на сегодняшний день является книга Дж. Фридла «Регулярные выражения», позволяющая, по словам автора, «научиться мыслить регулярными выражениями».

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

<?php
// Удаляем все символы кроме букв и цифр
$username = preg_replace("/[a-z0-9]/i", "", $_GET['username']);
?>

Можно заострить внимание посетителя на том, что введённые им данные содержат недопустимые символы, за счёт вывода в окно браузера «предупреждения».

<?php
// Проверяем все символы на буквы и цифры
if( !( preg_match("/^([a-z0-9]*)$/i", $_GET['username']) ) )
{
header("plz_die.php");
}
?>

Что предпринять — выбор за вами. Лучше написать о разрешенных символах где-нибудь рядом с соответствующим полем HTML-формы, иначе посетитель может вести свое имя и обнаружить что оно отображается не совсем так, как он ожидал (например Elvi$ как Elvi) и ему придется проходить процедуру регистрации повторно. Кроме того, посетитель может случайно вести неверный символ и устрашающий вид страницы plz_die.php, может его отпугнуть.

Замечание

Следует проверять ВСЕ переменные получаемые от пользователя — GET, POST, COOKIE. Во все из них без труда можно встроить зловредный код. Особое внимание следует уделять паролям, так как в них не принято вводить ограничения на символы.

PHP обладает несколькими функциями, позволяющими значительно облегчить задачу защиты Web-приложений. Одной из таких функций является htmlspecialchars() которая гарантирует, что любой введенный пользователем код (php, javascript и т.д.) будет отображен, но выполняться не будет.

Функция имеет следующий синтаксис:

string htmlspecialchars (string str [, int quote_style [, string charset]])

Через первый обязательный параметр str, функции передается обрабатываемый текст, который возвращается после преобразования как результат работы.
Второй необязательный параметр quote_style задаёт режим обработки одинарных и двойных кавычек. По умолчанию, данный параметр соответствует константе ENT_COMPAT, в данном режиме двойные кавычки заменяются символом «&quot;», при этом одиночные остаются без изменений. Кроме этого параметр может принимать два других значения: ENT_QUOTES и ENT_NOQUOTES. В первом случае, помимо двойных, кавычек преобразованию подвергаются так же одинарные кавычки, которые заменяются символом «'». Значение параметра ENT_NOQUOTES задаёт режим, в котором не один из видов кавычек не подвергается преобразованию.
Последний параметр charset определяет кодировку, например «cp1251″ или «KOI8-R».
Данная функция предназначена для отображения кода и HTML-разметки на Web-странице, но вводимые пользователем данные не мешает пропускать через неё, во избежание неприятностей.
Другой полезной функцией является stripslashes(), которая предназначена для удаления обратных слешей и имеет следующий синтаксис:

string stripslashes (string str)

Функция принимает единственный параметр str, с обрабатываемой строкой. Результатом работы функции является строка str в которой удаляются экранирующие бэкслэши (‘ преобразуется в ‘, двойные бэкслэши (\) преобразуется в одиночные() и т.д.).

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

Замечание

Подробнее об SQL-инъекциях можно почитать по ссылкам:
http://www.securitylab.ru/45438.html
http://www.securitylab.ru/49424.html

Пусть имеется база данных пользователей users, содержащая три поля: первичный ключ (id_user), имя пользователя (name) и его пароль (pass). SQL-оператор CREATE, создающий данную таблицу приведён ниже.

CREATE TABLE users (
id_user int(11) NOT NULL auto_increment,
name tinytext,
pass tinytext,
PRIMARY KEY (id_user)
) TYPE=MyISAM;
INSERT INTO users VALUES (1, 'user1', 'pass1');
INSERT INTO users VALUES (2, 'user2', 'pass2');
INSERT INTO users VALUES (3, 'user3', 'pass3');

Авторизация пользователей производится через HTML-форму.

<form action=handler.php method=post>
Имя посетителя : <input type=text name=name >
Пароль : <input type=password name=pass >
<input type=submit value=Отправить>
</form>

Обработчик handler.php проверяет соответствие введённого имени паролю

<?php
// Устанавливаем соединение с базой данных
include "config.php";
// Осуществляем соответствия имени паролю
$query = "SELECT * FROM users
WHERE name = '$_POST[name]' AND
pass = '$_POST[pass]'"
;
$usr = mysql_query($query);
if(!
$usr) exit("Ошибка в SQL-запросе");
if(
mysql_num_rows($usr)>0)
{
// Вход в защищённую область сайта
}
?>

Данный метод авторизации обходиться указанием следующего значения для пароля pass:

123' AND 1=1

Или даже еще проще, например вводом следующего значения в качестве имени name:

admin'/*

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

<?php
// добавляем слеши перед кавычками, чтобы они стали виде escape-последовательности,
// например ' замениться на ', " замениться на "
$_POST['name'] = addslashes($_POST['name']);
// заменяем все специальные символы эквивалентом
$_POST['name'] = htmlspecialchars ($_POST['name']);
// отрезаем все ненужные симовлы
$_POST['name'] = preg_replace("/[a-z0-9]/i", "", $_POST['name']);
?>

Может быть использован любой из них или их комбинации.

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

<?php
$id
= $_POST['id'];
$pass = $_POST['passw'];
$sql = "SELECT info FROM users WHERE user_id = $id AND pass = '$pass'"
// если  запрос не вернул результат, то пользователь не найден
if( !( mysql_num_rows( mysql_query( $sql ) ) ) )
{
echo
'Нет такого пользователя';
}
?>

Параметр $id злоумышленника тогда будет иметь такой вид:
1/*
ВСЕ!

Проблема числовых параметров, встала еще более остро, начиная с MYSQL 4, где появился SQL-оператор UNION, который позволяет объединить результаты нескольких запросов. Пусть страницу с информацией о пользователе формирует скрипт user.php, который принимает в качестве параметра первичный ключ id_user из таблицы users.

<?php
// Устанавливаем соединение с базой данных
include "config.php";
// Осуществляем запрос к базе данных,
// извлекая запись из таблицы user
if(isset($_GET['id_user']))
{
// Формируем SQL-запрос
$query = "SELECT * FROM users WHERE id_user = ".$_GET['id_user'];
// Выполняем SQL-запрос
$usr = mysql_query($query);
if(
$usr) exit("Ошибка при обращении к таблице пользователей");
$user = mysql_fetch_array($usr);
// Выводим имя пользователя
echo $user['name'];
}
?>

Результатом работы скрипта при обращении к странице по адресу users.php?id_user=1 является вывод имени посетителя: user1

Теперь помещая в адресную строку следующий адрес

http://localhost/user.php?id_user=-1%20UNION%20SELECT%201,pass,name%20FROM%20users%20WHERE%20id_user=1

можно получить пароль пользователя с первичным ключом id_user = 1: pass1.

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

<?php
if(!preg_match("|^[d]*$|", $_GET['id_theme'])) exit();
?>

Или при помощи функции is_numeric()

<?php
// Проверяем значение на число
if( !is_numeric($id) )
{
die(
"Неверное значение в строке запроса!");
}
// Или же так преобразуем значение
$id = intval($id);
?>

Третий вид атаки, который может быть произведён на сайт — это флуд, часто в автоматической форме.

Замечание

Размещение однотипных сообщений на форуме или гостевой книге на жаргоне Web-разработчиков называется флудом (от англ. flood — поток, избыток).

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

Замечание

Элемент суперглобального массива $_SERVER['REFERRER'] содержит адрес страницы с которой посетитель пришёл на данную страницу.

Проверка элемента суперглобального массива $_SERVER['REFERRER'] на предмет содержания в нём адреса исходного сайта, позволяет устранить данный вид атаки

<?php
// $site_addres можете поменять на свое
// но лучше определить его в "общем" файле
if ($_SERVER['REFERRER'] =! $site_address) die;
?>

Не стоит сильно надеяться на эту защиту, ведь REFERER формируется браузером, а значит, может быть подделан. Поэтому когда необходима более надёжная защита необходимо прошить HTML-форму сессией.

<?php
// Инициируем сессию
session_start();
?>
<form action=handler.php method=post>
Имя : <input type=text name=name>
Пароль : <iput type=password name=pass>
<input type=submit name=send value=Отправить>
<input type=hidden name=session_id value=<?php echo session_id();?>>
</form>

Как видно HTML-форма содержит дополнительное поле с именем session_id. После того как пользователь нажимает на кнопку «Отправить» данные отправляются обработчику:

<?php
// Инициируем сессию
session_start();
// Сравниваем переданный идентификатор из формы с
// текущим идентификатором сессии
if($_POST['session_id'] != session_id())
{
exit(
"Попытка передачи данных с другого хоста. Скрипт остановлен.");
}
// Дальнейшая обработка данных...
?>

В котором значение поля session_id проверяется с текущим идентификатором сессии.

Теперь несколько рекомендаций по защите сайта.

1. Не используйте проверку на возращение строки (mysql_num_rows()), а применяйте следующий подход:

<?php
$user
= $_POST['user'];
$pass = $_POST['pass'];
$sql = "SELECT user, pass FROM users WHERE user = '$user'";
list(
$m_user, $m_pass) = mysql_fetch_row( mysql_query($sql) );
if (
$pass != $m_pass or  // даст TRUE, если пароли не равны
$user != $m_user // данная проверка даст TRUE, если была sql инъекция
)
{
die(
"die");
}
?>

2. Не используйте на прямую foreach() для массивов переменных переданных от пользователя

<?php
foreach ($user as $k => $v)
{
$sql = "UPDATE users SET $k = $v WHERE user ='$user['name']'";
mysql_query($sql);
}
?>

Злоумышленник может подправить код и добавить еще один параметр к массиву.
Как альтернативу можно предложить следующий код:

<?php
$users_vars
= array('Date' => 'Date', 'City' => 'City', 'ZIP' => 'ZIP');
foreach (
$user as $k => $v)
{
if( isset(
$_POST[$users_vars[$k]]) )
{
$sql = "UPDATE users SET $k = $v WHERE user ='$user['name']'";
mysql_query($sql);
}
}
?>

3. Там где не требуется ввод большого объёма данных ограничивайте число вводимых в HTML-форму символов, за это несёт ответственность параметр maxlength тега input. Например, в следующем текстовом поле ввести можно не более 32 символов:

<input name="user" maxlength="32" value="">

Можно организовать проверку и непосредственно в скрипте

<?php
// Не будим доверять пользователю, ведь подправить значение maxlength
// можно и на локальной машине
substr($_POST['user'], 0, 32);
?>

И напоследок несколько блиц-советов:

  • лучше использовать «белый» список чем «черный» (Лучше разрешать только нужное, чем запрещать ненужное)
  • помните, что картинка может на самом деле оказать скриптом!
  • когда дело доходит до взлома, злоумышленник проявляет всю изобретательность, заложенную в него природой, в тоже время, когда дело доходит защиты, программист же демонстрирует верх скудоумия и однообразности, так как ему чисто психологически не хочется ломать собственное творение. Для тестирования своего сайта приглашайте сторонних людей.
  • если код Web-приложения неизвестен злоумышленнику, его гораздо сложнее ломать. Не ленитесь – напишите собственное Web-приложение, на своём уникальном движке – не используйте готовые широкоизвестные Web-приложения. В тоже время постарайтесь не выводить полный отчёт при возникновении ошибки по которому легко восстановить логику Web-приложения.

Вот теперь, пожалуй, и все. Конечно, затронуты далеко не все вопросы создания безопасного кода на PHP и не так подробоно, как хотелось бы…

Автор: Семенов А.Н., Симдянов И.В.
Источник: softtime.ru

Источник: http://www.php.su/articles/?cat=security&page=012

Оставить комментарий

Чтобы оставлять комментарии Вы должны быть авторизованы.

Похожие посты