Как защитить свой web ресурс. Примеры безопасных PHP скриптов.

Владимир | | PHP, Разное.

Защита web ресурса
Данные, полученные от посетителя сайта, нужно проверять. Это знают все, это постоянно повторяют во всех руководствах по программированию. Но в большинстве случаев сразу после проверки возникает вопрос: «А что делать, если данные не прошли проверку? Вывести сообщение об ошибке? Попытаться их исправить?».

В этой статье я покажу несколько приемов проверки и обработки входных данных.

Но, прежде всего, разберемся, какие типы некорректных данных мы можем получить от пользователя. Их всего два:
1) случайные ошибки;
2) специально сформированные запросы для взлома сайта.

В первом случае нужно объяснить посетителю, как исправить ошибку. Т.е. отправить его на страницу с инструкцией (или показать текст около поля ввода).

Второй случай немного сложнее. Тут главное помнить одно простое правило: «Никогда не доверяй данным, полученным от браузера». И не важно, выполняется ли проверка с помощью JavaScript или нет, имея минимальные знания программирования можно сформировать и отправить любой запрос. Отсюда вывод – все данные должны быть проверены на стороне сервера.

На сегодняшний день наиболее часто применяется два вида атак на сайт. Это SQL Injection и Cross Site Scripting (XSS). Рассмотрим их немного подробнее.

SQL Injection

Суть этой атаки заключается в попытке изменить структуру запроса к БД. Классический пример – обход регистрации на сайте.
Допустим, для проверки имени и пароля используется запрос:

SELECT * FROM users WHERE username = '".$_POST['username']."' AND password = '".$_POST['password']

Данные в этот запрос вставляются непосредственно из массива $_POST, т.е. безо всяких проверок. Отсюда возникает проблема. Если посетитель введет вместо имени «admin' --», то запрос станет немного короче:

SELECT * FROM users WHERE username = 'admin'

Произойдет это потому, что символ «-» означает начало комментария. И, таким образом, «AND password…» выполняться не будет.

Защититься от этой атаки несложно. Стандартная библиотека PHP содержит функцию mysql_real_escape_string(), которая экранирует спецсимволы SQL.

Если мы используем эту функцию в предыдущем запросе

SELECT * FROM users WHERE username = '".mysql_real_escape_string($_POST['username'])."' AND password = '".mysql_real_escape_string($_POST['password'])

то обойти проверку пароля уже не удастся.

Кроме того, многие фрэймворки имеют свои библиотеки для работы с БД, которые автоматически выполняют экранирование символов.

Например, в CodeIgniter запрос можно отправить так:

$sql = "SELECT * FROM users WHERE username = ? AND password = ?";
$this->db->query($sql, array($_POST['username'], $_POST['password']));

Метод query() в запрос вместо знаков «?» подставит значения из массива, переданного во втором параметре. При этом все спецсимволы автоматически будут экранированы.

Как видите, защита от SQL Injection сводится к экранированию спецсимволов SQL.

Cross Site Scripting (XSS)

Эта атака заключается в том, что пользователь вставляет на страницу вашего сайта свой html (js) код. Представьте обычный форум. Что произойдет если не проверять сообщения, которые отправляют посетители?

В этом случае можно будет вставить сообщение вроде:

Привет, <script language="JavaScript" type="text/javascript">window.location = "http://www.my_cool_site.com/";</script>

Такое сообщение будет отправлять всех посетителей форума на www.my_cool_site.com. Удобный способ увеличить количество посетителей собственного сайта, не правда ли 🙂 .

Естественно, может быть масса вариантов этой атаки, от обычной порчи дизайна, до кражи персональных данных.

Теперь рассмотрим способы защиты.

1) Вообще запретить html теги. Т.е. преобразовать спецсимволы html в эскейп последовательности. Этот метод легко реализуется.
Достаточно обработать полученные данные с помощью функции htmlspecialchars(). Напрмер:

$safeText = htmlspecialchars($_POST[‘text’]);

При этом символы «<» и «>» будут преобразованы в «<» и «>», т.е. все теги будут считаться обычным текстом.

Примечание. Функция htmlspecialchars имеет еще несколько параметров, о которых можно почитать здесь.

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

2) Использовать альтернативный язык разметки. Например, BBCode. Сейчас это язык применяется практически на всех форумах. Теги в BBCode заключаются в квадратные скобки «[, ]», а не угловые «<, >» как в html. Естественно, при формировании страницы происходит преобразование этих тегов в обычные (html).

Защита обеспечивается тем, что BBCode содержит очень ограниченный набор тегов (по сравнению с html). Например, тег [script] отсутствует.

Естественно, все html теги либо удаляются, либо обрабатываются по первому способу.

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

Примечание. Подробнее о BBCode можно почитать здесь.

3) Использование ограниченного набора html тегов. Суть этого метода заключается в том, что отсеивается только часть html тегов. При этом нужно учитывать все возможные варианты использования html тегов. Например, JavaScript код может быть вставлен параметр onClick тега <a>.

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

Единственный минус при использовании таких библиотек – это дополнительное потребление ресурсов.

Заключение

Как видите, защитить сайт не так уж и сложно, главное не откладывать вопросы безопасности. Но учтите, что кроме «дыр» в php коде существуют еще уязвимости web сервера и операционной системы. К сожалению, если у вас нет собственного сервера, то в этих вопросах придется полагаться на службу техподдержки хостера.

И, самое главное, «человеческий фактор». Тот же форум не сможет нормально существовать без модераторов, его могут просто «заспамить» без всяких XSS атак.

До встречи!

  • в CodeIgniter правильнее еще брать параметры не пост, а например

    $this->input->post( 'username', TRUE );

    • Согласен. Но я бы сказал не говорил «правильнее», т.к. второй параметр (TRUE) пропускает данные через встроенный XSS фильтр.
      Если вы используете библиотеку вроде HTML Purifier, то в двойной проверке особенного смысла нет.

      С другой стороны,
      $this->input->post( ‘username’ );
      возвращает FALSE если параметр ‘username’ не установлен, а это удобно.

      • Хм… Не рассмотрены ситуации с запросами вида
        index.php?open=http://anothersite.com/xss.php
        (открытие внешних источников),
        и запросами вида
        index.php?open=../../config.php (открытие локальных файлов)

        а также ничего не сказано, например, про проверку типов заливаемых файлов (я заливаю как аватарку php файл, а потом вызываю что-то вида http://victim.org/project/images/users/112233abcd.php). При неправильной настройке сервера тоже работает 🙂 Сам сталкивался.

        Далее, забыты такие вещи как, например, использование простых паролей для администраторского входа (очень много взломов админки — из-за использований стандартного «admin/admin»).

        И забыто самое главное.
        Защита сайта (и вообще ИБ) — это не действие, а процесс. 🙂

        • Отвечаю по-порядку.

          Не рассмотрены ситуации с запросами вида
          index.php?open=http://anothersite.com/xss.php

          Сначала нужно решить разрешать такие ссылки или нет. Если нет, то можно проверять наличие «?» и выводить сообщение об ошибке или еще что-нибудь…

          проверка типов заливаемых файлов

          На эту тему я собирался написать небольшую статью 🙂
          В CI есть библиотек upload.
          При создании загрузчика можно указать разрешенные типы файлов, ограничения по размеру и т.п.
          Например,
          $config['allowed_types'] = 'gif|jpg|png';
          $this->load->library('upload', $config);
          Я пользовался, очень удобно.

          использование простых паролей

          О «человеческом факторе» я написал (самый конец статьи).

          не действие, а процесс

          Спорить сложно 🙂

  • в CodeIgniter правильнее еще брать параметры не пост, а например

    $this->input->post( 'username', TRUE );

    • Согласен. Но я бы сказал не говорил «правильнее», т.к. второй параметр (TRUE) пропускает данные через встроенный XSS фильтр.
      Если вы используете библиотеку вроде HTML Purifier, то в двойной проверке особенного смысла нет.

      С другой стороны,
      $this->input->post( ‘username’ );
      возвращает FALSE если параметр ‘username’ не установлен, а это удобно.

      • Хм… Не рассмотрены ситуации с запросами вида
        index.php?open=http://anothersite.com/xss.php
        (открытие внешних источников),
        и запросами вида
        index.php?open=../../config.php (открытие локальных файлов)

        а также ничего не сказано, например, про проверку типов заливаемых файлов (я заливаю как аватарку php файл, а потом вызываю что-то вида http://victim.org/project/images/users/112233abcd.php). При неправильной настройке сервера тоже работает 🙂 Сам сталкивался.

        Далее, забыты такие вещи как, например, использование простых паролей для администраторского входа (очень много взломов админки — из-за использований стандартного «admin/admin»).

        И забыто самое главное.
        Защита сайта (и вообще ИБ) — это не действие, а процесс. 🙂

        • Отвечаю по-порядку.

          Не рассмотрены ситуации с запросами вида
          index.php?open=http://anothersite.com/xss.php

          Сначала нужно решить разрешать такие ссылки или нет. Если нет, то можно проверять наличие «?» и выводить сообщение об ошибке или еще что-нибудь…

          проверка типов заливаемых файлов

          На эту тему я собирался написать небольшую статью 🙂
          В CI есть библиотек upload.
          При создании загрузчика можно указать разрешенные типы файлов, ограничения по размеру и т.п.
          Например,
          $config['allowed_types'] = 'gif|jpg|png';
          $this->load->library('upload', $config);
          Я пользовался, очень удобно.

          использование простых паролей

          О «человеческом факторе» я написал (самый конец статьи).

          не действие, а процесс

          Спорить сложно 🙂

  • Спасибо за информыцию, как раз искал.

  • Спасибо за информыцию, как раз искал.

  • Николай

    Сначала нужно решить разрешать такие ссылки или нет. Если нет, то можно проверять наличие «?» и выводить сообщение об ошибке или еще что-нибудь…

    что делать есди ДА? разрешены?

    • Хороший вопрос 🙂

      Давайте немного уточним задачу.
      Ссылка
      index.php?open=http://anothersite.com/xss.php
      сама по себе ничего не делает. Если посетитель кликнет по ней, то вашему скрипту index.php будет отправлен параметр open со значением http: //anothersite.com/xss.php

      А что делает скрипт? Получает содержимое указанной страницы и обрабатывает его или отправляет посетителю редирект на эту страницу.

      В первом случае нужно обработать полученный контент, как минимум, убрать все скрипты.

      Во втором — вы ничего сделать не сможете, т.к. изменить чужой сайт не получится. Разве что предупредить посетителя, что он покидает ваш ресурс.

  • Николай

    Сначала нужно решить разрешать такие ссылки или нет. Если нет, то можно проверять наличие «?» и выводить сообщение об ошибке или еще что-нибудь…

    что делать есди ДА? разрешены?

    • Хороший вопрос 🙂

      Давайте немного уточним задачу.
      Ссылка
      index.php?open=http://anothersite.com/xss.php
      сама по себе ничего не делает. Если посетитель кликнет по ней, то вашему скрипту index.php будет отправлен параметр open со значением http: //anothersite.com/xss.php

      А что делает скрипт? Получает содержимое указанной страницы и обрабатывает его или отправляет посетителю редирект на эту страницу.

      В первом случае нужно обработать полученный контент, как минимум, убрать все скрипты.

      Во втором — вы ничего сделать не сможете, т.к. изменить чужой сайт не получится. Разве что предупредить посетителя, что он покидает ваш ресурс.

  • vvpol

    По поводу защиты от XSS атак. Не всегда есть возможность вырезать или преобразовывать html тэги. Например пользовательский ввод через wysiwyg. Здесь необходимо анализировать данные на сервере. Подробнее Здесь

    • Так в статье же речь идет только о проверках на стороне сервера.

  • vvpol

    По поводу защиты от XSS атак. Не всегда есть возможность вырезать или преобразовывать html тэги. Например пользовательский ввод через wysiwyg. Здесь необходимо анализировать данные на сервере. Подробнее Здесь

    • Так в статье же речь идет только о проверках на стороне сервера.

  • vvpol

    Не совсем понял о какой статье идет речь. Об обсуждаемой или о предложенной по ссылке. В них в обоих идет речь о проверке на сервере. Глупо утверждать, что сколько-нибудь надежную проверку можно сделать на клиенте. В статье по ссылке идет речь о проверке именно корректности html кода, а не строковых даныых. Еще раз подчеркну: не о вырезании или замене тэгов, а именно проверке наличия недопустимых тэгов и атрибутов

    • Я имел ввиду свою статью.

      не о вырезании или замене тэгов, а именно проверке наличия недопустимых тэгов и атрибутов

      Хорошо, нашли мы недопустимые теги. Что дальше?
      На мой взгляд вариантов два, либо вырезать, либо вообще отклонить запрос.
      По-моему вторая часть моей статьи «Cross Site Scripting (XSS)» как раз на эту тему.

  • vvpol

    Не совсем понял о какой статье идет речь. Об обсуждаемой или о предложенной по ссылке. В них в обоих идет речь о проверке на сервере. Глупо утверждать, что сколько-нибудь надежную проверку можно сделать на клиенте. В статье по ссылке идет речь о проверке именно корректности html кода, а не строковых даныых. Еще раз подчеркну: не о вырезании или замене тэгов, а именно проверке наличия недопустимых тэгов и атрибутов

    • Я имел ввиду свою статью.

      не о вырезании или замене тэгов, а именно проверке наличия недопустимых тэгов и атрибутов

      Хорошо, нашли мы недопустимые теги. Что дальше?
      На мой взгляд вариантов два, либо вырезать, либо вообще отклонить запрос.
      По-моему вторая часть моей статьи «Cross Site Scripting (XSS)» как раз на эту тему.

  • vvpol

    Я считаю, что правильный — второй вариант: отклонить запрос.
    Без уведомления пользователя, вообще некорректно искажать(вырезать, преобразовывать) его данные. Целью моего комментария было предложение способа обнаружения неразрешенных тэгов более простого чем HTML Purifier и, соответсвенно, менее ресурсоемкого. Хотя ресурсоемкость в этом деле несущественна, поскольку, даже на раскрученых форумах, добавление комменариев — не массовая операция.

    • Не думаю, что тут есть правильный вариант. Могут быть разные ситуации.
      Например, вы хотите в сайдбаре показать виджет с последними комментариями. Но естественно не полными, а, например, только первые 10 слов.
      Что будет если посетитель выделил слова с девятого по одиннадцатое жирным. Если просто обрезать текст — получим не закрытый тег.
      Кроме того, может быть ситуация когда посетитель вставляет в WYSIWYG редактор текст из Word, и он отправляется со всех кучей тегов.
      В таких случаях библиотеки вроде HTML Purifier очень помогают.

  • vvpol

    Я считаю, что правильный — второй вариант: отклонить запрос.
    Без уведомления пользователя, вообще некорректно искажать(вырезать, преобразовывать) его данные. Целью моего комментария было предложение способа обнаружения неразрешенных тэгов более простого чем HTML Purifier и, соответсвенно, менее ресурсоемкого. Хотя ресурсоемкость в этом деле несущественна, поскольку, даже на раскрученых форумах, добавление комменариев — не массовая операция.

    • Не думаю, что тут есть правильный вариант. Могут быть разные ситуации.
      Например, вы хотите в сайдбаре показать виджет с последними комментариями. Но естественно не полными, а, например, только первые 10 слов.
      Что будет если посетитель выделил слова с девятого по одиннадцатое жирным. Если просто обрезать текст — получим не закрытый тег.
      Кроме того, может быть ситуация когда посетитель вставляет в WYSIWYG редактор текст из Word, и он отправляется со всех кучей тегов.
      В таких случаях библиотеки вроде HTML Purifier очень помогают.

  • vvpol

    Например, вы хотите в сайдбаре показать виджет с последними комментариями…

    это не пользовательский ввод…
    По поводу Word-а никаких проблем, там все под классы и корректно ни в один wysiwyg он не войдет, в смысе, пользователь сам увидит, что ничего не получилось.
    По поводу RSS… честно говоря, не знал что там html форматированый текст гонят…
    А вот за идею корректной обрезки форматированного текста — Вам огромный respect. На вскидку, все ложится отлично. К среде выложу тест.
    Кажется, мы уже далеко ушли от темы xss %o)

    • Да, это точно 🙂 XSS касается только удаления скриптов из пользовательского ввода, порча дизайна — отдельная тема… но тоже важная 😉

  • vvpol

    Например, вы хотите в сайдбаре показать виджет с последними комментариями…

    это не пользовательский ввод…
    По поводу Word-а никаких проблем, там все под классы и корректно ни в один wysiwyg он не войдет, в смысе, пользователь сам увидит, что ничего не получилось.
    По поводу RSS… честно говоря, не знал что там html форматированый текст гонят…
    А вот за идею корректной обрезки форматированного текста — Вам огромный respect. На вскидку, все ложится отлично. К среде выложу тест.
    Кажется, мы уже далеко ушли от темы xss %o)

    • Да, это точно 🙂 XSS касается только удаления скриптов из пользовательского ввода, порча дизайна — отдельная тема… но тоже важная 😉

  • Интересно. Я только начинаю программировать на PHP. Я так понимаю вместо предложенного автором способа ($safeText = htmlspecialchars($_POST[‘text’]);), чтобы не создавать дополнительную переменную можно использовать, например, $Name = htmlspecialchars($_POST[‘Name’]); или $Text = htmlspecialchars($_POST[‘Text’]);, таким образом скрипт также будет возвращать введенное значение пользователем в окно формы с заменой спец символов.

    • Вы, наверное, имели ввиду «не создавать переменную с другим именем».
      В вашем случае переменная $Name существует отдельно от элемента массива $_POST[‘Name’].

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

      Нет, так просто автозаполнение формы сделать не получится.
      Нужно добавить примерно такой код
      input …. value=»<?php echo $Name; ?>»…

      • Да, именно так я и сделал.

  • Интересно. Я только начинаю программировать на PHP. Я так понимаю вместо предложенного автором способа ($safeText = htmlspecialchars($_POST[‘text’]);), чтобы не создавать дополнительную переменную можно использовать, например, $Name = htmlspecialchars($_POST[‘Name’]); или $Text = htmlspecialchars($_POST[‘Text’]);, таким образом скрипт также будет возвращать введенное значение пользователем в окно формы с заменой спец символов.

    • Вы, наверное, имели ввиду «не создавать переменную с другим именем».
      В вашем случае переменная $Name существует отдельно от элемента массива $_POST[‘Name’].

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

      Нет, так просто автозаполнение формы сделать не получится.
      Нужно добавить примерно такой код
      input …. value=»<?php echo $Name; ?>»…

      • Да, именно так я и сделал.