Pull to refresh

12 навыков создания защищенных веб-приложений

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

Большинство примеров кода не привязаны к какому-либо конкретному языку программирования, но для наглядности я буду использовать PHP.

Итак, поехали.



1. Защита от SQL injection


Допустим у вас есть вебсайт с формой ввода имени пользователя. Для проверки наличия имени в базе данных вы используте вот такой код:
$query = "SELECT * FROM `Users` WHERE UserName='" . $_POST["Username"]. "'";
mysql_query($query);

где $_POST[«Username»] — введенное пользователем имя.

Пользователю достаточно ввести вот такое значение в поле Username
' or '1'='1

чтобы получился запрос, который всегда возвращает данные:
SELECT * FROM `Users` WHERE UserName = '' OR '1'='1'

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

Пример такого ввода:
a';DROP TABLE `Users`; SELECT * FROM `userinfo` WHERE 't' = 't


Запрос на выходе:
SELECT * FROM `Users` WHERE `UserName` = 'a';DROP TABLE `Users`; SELECT * FROM `userinfo` WHERE 't' = 't'

Два основных способа избежать SQL injection:
  • параметризованные запросы

Наиболее надежный метод, но не всегда подходит. В PHP можно для этой цели использовать MySQLi
$stmt = $db->prepare('update people set name = ? where id = ?');
$stmt->bind_param('si',$name,$id);
$stmt->execute();

  • escaping

В PHP для этого есть функция mysql_real_escape_string, которая заменит опасные символы на escape последовательности. Наш пример теперь будет выглядеть вот так:
$query = sprintf("SELECT * FROM `Users` WHERE UserName='%s'",
mysql_real_escape_string($_POST["Username"]));
mysql_query($query);


2. Защита от Сross Site Scripting (XSS)


XSS уязвимости могут быть подвержены динамические вебсайты, где пользователи вводят какие-то свои данные, которые потом будут показаны на странице: форумы, гостевые книги, комментарии блогам и другое. Идея XSS заключается во встраивании в текст комментария какого-то Javascript кода, который исполнится, когда страницу откроет другой пользователь.

Чем может навредить безобидный джаваскрипт? Довольно многим, от открытие левых сайтов в попапе или простого завешивания браузера до воровства куки. Последнее уже может привести к выполнению другим пользователем действий на этом сайте от вашего имени. Рассмотрим минимальный пример.

Форма для ввода текста:
<form id="myFrom" action="showResults.php" method="post">
<div><textarea name="myText" rows="4" cols="30"></textarea><br />
<input type="submit" value="Submit" name="submit" /></div>
</form>


Файл showResults.php:
echo("You typed this:");
echo($_POST['myText']);


Мы видим, что введенный текст никак не обрабатывается и выводится на страницу в исходном виде. Теперь рассмотрим такой пример ввода:


Нетрудно видеть, что javascript код исполнится после сабмита формы. Лечится пропусканием ввода через htmlentities() непосредственно перед показом:
echo("You typed this:");
echo(htmlentities($_POST['myText']));


3. Использование HTTPS


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

4. Предотвращение скачивания пользовательских файлов по прямой ссылке



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

Есть закачивать все файлы в директорию вида public_html/files, то файл mysecretdoc.pdf будет доступен любому желающему по прямой ссылке mysecurewebsite.com/files/mysecretdoc.pdf.

Есть как минимум два способа предотвратить эту ситуацию:

  • вынеcти директорию files на уровень выше, чтобы она была вне корня вебсайта и не была доступна через веб
  • использовать .htaccess для запрета прямого скачивания


5. Хранение паролей пользователей


— Не хранить пароли открытым текстом

Достаточно очевидное решение. Если мы будем хранить хэши паролей (MD5+salt), последстивия утечки таблицы паролей становятся намного менее серьезными, особенно в сочетании со следующим пунктом.

— Требовать, чтобы пароли удовлетворяли определенными правилам сложности и заставлять менять их через какое-то время. Как пользователь я не очень люблю этот метод, но он работает.

— Использовать комбинацию пароля (пин-кода) и устройства типа RSA токена для логина. Подойдет для банковских или внутрикорпоративных приложений.

— Сделать авторизацию через сторонний сервис, такой как Facebook, Twitter или OpenID. Пусть у них болит голова как уберечь пароли.

6. Шифрование и обфускация кода


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

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

Шифрование кода для PHP: ionCube, ZendGuard, SourceGuardian

Обфускация: Thicket Obfuscator for PHP

7. Шифрование данных


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

Технически ничего сложного здесь нет. Шифрование/декодирование можно реализвать как средствами языка программирования так и в самой БД. Второй метод предпочтительнее, особенно если нужно реализовать поиск по зашифрованным данным.

Вот как это можно сделать для MySQL.

Шифрование с помощью триггеров
delimiter |

CREATE TRIGGER insert_encrypt BEFORE INSERT ON cars
  FOR EACH ROW BEGIN
    SET NEW.Model = AES_ENCRYPT(NEW.Model,"my passphrase");
  END;
|

delimiter |

CREATE TRIGGER update_encrypt BEFORE UPDATE ON cars
  FOR EACH ROW BEGIN
    SET NEW.Model = AES_ENCRYPT(NEW.Model,"my passphrase");
  END;
|

Декодирование в SQL запросе
SELECT
...
AES_DECRYPT(Model,"my passphrase"),
...
FROM carscars

Бонус для параноиков

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

Из минусов:
— при смене пароля придется перешифровать данные
— в случае утери пароля восстановить данные не получится

8. Защита данных сессии (PHP, shared server)


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

Содержание типичного файла сессии PHP:
userName|s:5:"admin";accountNumber|s:9:"123456789";

Решение:
— шифровать переменные сессии
— хранить данные сессии в БД. В PHP можно переопределить обработчик сессии с помощью функции session_set_save_handler


9. Обработка сообщений об ошибках



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

Как минимум, сообщения об ошибках стоит отключить. В PHP это можно сделать вот так:
error_reporting(0);
@ini_set('display_errors', 0);


Наиболее же правильный метод это перехват сообщений об ошибке, запись их в БД, отправка уведомления разработчику итд. В PHP перехват сообщений об шибках делается с помощью функции set_error_handler(). И вот еще пример перехвата фатальных ошибок, которые нельзя перехватить с помощью set_error_handler().

10. Защита соединения между базой данных и приложением


Применимо к ситуации когда база данных расположена на другом сервере. Вот статья, которая рассказывает как создать SSL тоннель между MySQL и PHP.

11. Защита от form spoofing


Допустим у вас есть форма редактирования данных пользователя вот с таким УРЛ: example.com/edit_user.php?id=12345. Ничто не мешает пользователю 12345 поменять номер аккаунта в УРЛ и попытаться отредактировать другого пользователя. Простая проверка на стороне сервера пресекает эти попытки на корню.

Неискушенный прграммист может подумать, что заменив GET на POST мы избавимся от номеров аккаунтов в УРЛ и закроем уязвимость. Разумеется это не так. Сохранив страницу на свой компьютер и изменив данные формы, злоумышленник может подделать POST запрос.

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

Эта тема плотно перекликается с валидацией всех данных, вводимых пользователем. Допустим у вас есть радио-кнопка с выбором пола.
<input name="gender" type="radio" value="m" />Male
<input name="gender" type="radio" value="f" />Female

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

Злоумышленник может сохранить эту страницу себе на диск и поменять ее следующим образом.

<input name="gender" type="text" value="m';DROP TABLE `Users`; ... " />

Даже если вы обработаете ввод с помощью mysql_real_escape_string(), значение все равно получится слишком длинным для односимвольного поля и поломает запрос (вот для чего мы советуем отключать сообщения об ошибках в продакшен версии).

Хорошим решением будет усечение этого поля до одного символа:
substr($_POST['gender'],0,1)


12. Защита от Cross-site request forgery (CSRF)


Эта уязвимость менее известна чем XSS, хотя не менее опасна. Представим себе форум, где участник Vasya постит сообщние, содержащее вот такой вот код:
<img src="http://mysecurebank.com/withdraw?account=petya&amount=1000000&for=vasya" />

Когда эту страницу откроет участник Petya, браузер исполнит запрос
http://mysecurebank.com/withdraw?account=petya&amount=1000000&for=vasya

Проблема в том, что если банк хранит данные доступа в куках, Petya будет залогинен автоматически и транзакция совершится от его имени, о чем он, конечно, и не подозревает.

Первая мысль которая приходит в голову — заметить GET на POST на всех важных формах (или вообще на всех). К сожалению это не решает проблему полностью. Ничто не мешает злоумышленнику разместить вот такую форму на своем вебсайте:
<form id="f" action="http://mysecurebank.com/withdraw" method="post">
        <input name="account" value="petya" />
        <input name="amount" value="1000000" />
        <input name="for" value="vasya" />
    </form>

Если Васе удастся заманить Петю на свой вебсайт — форма будет отправлена и цель достигнута. Еще одна причина не ходить по подозрительным вебсайтам.

Как с этим бороться?
  • проверять значение HTTP реферера. Должно быть всегда с вашего собственного вебсайта. К сожалению положиться на это дело полностбю нельзя, так как многие прокси сервера могут его не передавать. К тому же его не так сложно подделать.
  • использовать скрытое поле в форме с секретным значением, как правило привязанным к сессии пользователя. Злоумышленник не может прочитать форму от имени Пети, поэтому секретное значение окажется для него неизвестным (XmlHttpRequest не может выполнить запрос к другому серверу).
  • Дополнительно отправлять куки через форму (прочитать с помощью джаваскрипта и вставить в форму). Если куки переданные через форму не совпадают с куками из заголовка — транзакцию не проводить.
  • Ограничение времени жизни кук

Дополнительная информация:
http://en.wikipedia.org/wiki/Cross-site_request_forgery
http://www.codinghorror.com/blog/2008/09/cross-site-request-forgeries-and-you.html

Заключение


Теперь хорошие новости. От большинства этих уязвимостей несложно защититься. Многие PHP фреймворки (Yii, CakePHP, CodeIgniter, Zend, Symfony) и генераторы кода (PHPRunner) имеют встроенную защиту от большиства уязвимостей. Тем не менее, стоит понимать, как оно работает, чем чревато и как защититься. Предупрежден — значит вооружен.

 
Tags:
Hubs:
+160
Comments194

Articles

Change theme settings