Виртуальный мир, как и реальный, — не самое безопасное место. Сайты в Интернете, подобно домам, подвержены взломам.
Их можно атаковать, из них можно вытягивать данные, в них можно вызывать сбои, ими можно манипулировать. А самое главное — пользователи зараженных сайтов могут стать разменной монетой в темных играх злоумышленников.
Персональные данные и денежные средства могут быть украдены посредством множества ухищрений, эксплуатирующих многочисленные уязвимости сайтов.
Именно о таких уязвимостях и пойдет речь в этой статье. Мы разберем самые распространенные угрозы, с которыми может столкнуться любой сайт в сети Интернет.
В дополнение к этому мы рассмотрим примеры небезопасных реализаций и способы устранения найденных уязвимостей — все, что необходимо для повышения безопасности веб-сайта.
В показанных примерах реализация backend-логики и организация безопасности сайта выполнена с помощью платформы Node.js в связке с библиотекой Express.js.
Серверы 152-ФЗ
полностью отвечает закону о защите
персональных данных.
Инъекция кода (Code Injection)
Инъекция кода (Code Injection) — уязвимость, позволяющая внедрять на сайт вредоносный код извне и выполнять его как системный.
С помощью инъекций кода можно управлять удаленным хостом и выуживать приватные данные, которые на нем хранятся.
Для выполнения инъекции необходимо наличие уязвимого входного канала — места, через которое, подобно уколу с ядом, проникает вредоносный код. В протоколе HTTP есть несколько таких мест:
GET-запрос. Вредоносный код размещается в query-строке GET-запроса:
POST-запрос. Вредоносный код размещается в теле POST-запроса:
HTTP-заголовки. Вредоносный код размещается в одном из HTTP-заголовков:
Это самые распространенные места, но не все — вредоносный код может быть везде, где удаленный сервер читает и использует данные. А сама уязвимость возникает именно тогда, когда логика приложения использует прочитанные данные без какой-либо валидации.
Тем не менее, инъекция кода — широкое понятие. Существует множество видов инъекций, но все они работают по одинаковому принципу.
SQL-инъекция
SQL-инъекция (SQL injection) — разновидность уязвимости инъекции кода, позволяющая выполнять произвольные SQL-запросы на стороне удаленного сервера.
С помощью таких запросов можно управлять таблицами базы данных: читать, изменять, удалять, создавать.
Проблема: сервер имеет небезопасный обработчик GET-запросов, который не валидирует полученные query-параметры и тем самым делает возможным выполнение SQL-инъекции:
В этом случае злоумышленник может отправить к удаленному серверу GET-запрос, содержащий SQL-выражение в query-строке:
То есть запрос выполняется по следующему адресу:
Таким образом, SQL-запрос, извлекающий данные:
Превратится в SQL-запрос, извлекающий данные и удаляющий таблицу:
Подобно открытой форточке, злоумышленник может просунуть в это окошко любой необходимый ему инструмент, получив карт-бланш на управление базой данных на стороне удаленного сервера.
Решение: защититься от SQL-инъекций можно через многоуровневую валидацию данных:
- Разделение кода и данных. Высокоуровневые библиотеки (JPA/Hibernate, Entity Framework, Django ORM) формируют слой ORM-абстракций (Object-Relational Mapping), которые позволяют работать с базами данных в представлении параметризованных объектов, а не прямых текстовых SQL-запросов. Вместо библиотек можно подготовить отдельные функции (или классы), генерирующие SQL-выражения на основе шаблонов и данных. Главное — не использовать конкатенацию строк для формирования SQL-запросов.
- Валидация и фильтрация ввода. Каждый параметр проверяется на наличие допустимых значений с помощью регулярных выражений, числовых диапазонов, наборов разрешенных слов, ограничений длины и типов данных.
- Контекстное экранирование. Замена определенных символов в данных, полученных от пользователя, на другие. Например, переменную из query-строки можно обернуть в одиночные кавычки, после чего она будет распознана SQL-движком не как опасная команда, а как безопасный строковый литерал.
- Минимизация прав БД. Серверное приложение получает доступ к базе данных только с минимально необходимыми правами, а пользователь, от имени которого запускается сервер, вообще не имеет прав на системные папки и критические команды.
- Автоматизированное тестирование. Инструменты автоматического анализа (SAST) и динамического сканирования (DAST) выявляют потенциальные инъекции еще до стадии продакшена, а пенетрационное тестирование (пентесты) симулирует реальные атаки для проверки применяемых мер защиты.
Следуя описанным мерам безопасности сайта, код, показанный ранее, можно преобразовать в более защищенную форму обработки пользовательских запросов:
Таким образом, комплексная валидация данных, полученных извне, исключает возможность выполнения SQL-инъекции и тем самым обеспечивает безопасность сайта в целом.
Cross-Site Scripting (XSS)
Межсайтовый скриптинг (Cross-Site Scripting, XSS) — разновидность уязвимости инъекции кода, позволяющая выполнять произвольный JavaScript-код в браузере другого пользователя.
Такой код может перехватывать сессии, подменять содержимое страниц, перенаправлять на фишинговые сайты — варианты зависят от фантазии злоумышленника.
В акрониме XSS используется X, чтобы не путаться с CSS — Cascading Style Sheets.
Есть несколько видов межсайтового скриптинга:
- Reflected (Отраженный). Вредоносный код передается пользователю через специально сформированную ссылку и «отражается» сервером в ответе.
- Stored (Хранимый). Вредоносный код сохраняется на стороне сервера и автоматически показывается всем посетителям.
- DOM-base (DOM-модель). Вредоносный код внедряется в браузер пользователя за счет уязвимостей в клиентском JavaScript-коде и выполняется без прямого участия сервера.
Проблема Reflected: Сервер генерирует HTML-ответ на основе невалидированных пользовательских данных, тем самым делает возможным выполнение отраженного XSS:
В этом случае злоумышленник может отправить к удаленному серверу GET-запрос, который будет содержать в query-строке разметку с вредоносным кодом на JavaScript:
Проще говоря, запрос выполняется по следующему адресу:
Выполнение отраженного межсайтового скриптинга требует от злоумышленника навыков социальной инженерии — он должен будет убедить неосторожного пользователя перейти по измененной ссылке.
В этом случае содержание открывшейся страницы примет следующий вид:
Таким образом, уязвимый для XSS сайт способен превратиться в идеальный инструмент для выполнения вредоносного кода в браузере доверчивого пользователя.
Решение для Reflected: подобно SQL-инъекциям, для защиты от XSS необходимы методы валидации отображаемых данных:
- Контекстная валидация данных. Параметры проверяются на соответствие допустимым значениям с помощью регулярных выражений, ограничений длины и типизированных преобразований. Возможно, это один из самых эффективных методов обеспечения безопасности веб-сайта.
- Контекстное экранирование. Замена одних символов на другие для предотвращения исполнения вредоносного кода в неподходящем для него месте.
- Безопасное API для работы с DOM. Использование
innerTextилиtextContentвместоinnerHTMLдля трактовки вставляемых HTML-данных как текста, а не как кода. В случае с атрибутами задействование функцииelement.setAttribute('value', userValue)вместо явных конструкций<input value="${userValue}">. - Шаблонизаторы и фреймворки. Использование шаблонизаторов или фреймворков, в которых встроено автоматическое экранирование.
Используя эти методы, можно повысить безопасность показанного ранее кода, защитив его от выполнения отраженного XSS:
В модифицированном примере HTML-экранирование добавлено для демонстрации дополнительной меры защиты, хотя оно не обязательно — регулярное выражение и без того отсекает все запрещенные символы.
Однако без использования регулярного выражения экранирование превратило бы опасную строку:
В безопасный набор символов:
Таким образом запуск потенциально вредоносного кода в браузере пользователя был бы полностью предотвращен.
Проблема Stored: код, уязвимый к межсайтовому скриптингу хранимого типа, практически ничем не отличается от показанного ранее, с одним лишь исключением — результат отправляется пользователю не напрямую, а через базу данных:
В такой реализации есть два серьезных недостатка — отсутствие валидации во время добавления комментария и отсутствие валидации во время вывода добавленных комментариев.
То есть комментарии с вредоносным кодом не только попадут в хранилище, но и будут регулярно отправляться пользователям, открывающим сайт.
В этом случае навыки социальной инженерии злоумышленнику не нужны совсем — посетители уязвимого сайта самостоятельно запустят вредоносный код.
Решение для Stored: чтобы убрать уязвимость XSS хранимого типа, необходимо добавить валидацию и фильтрацию пользовательских данных в тех местах, где они проходят через систему.
Иными словами, нужно сделать так, чтобы вредоносный код не попадал внутрь и не выходил изнутри. Для этого базовая логика добавления и вывода комментариев требует некоторой доработки:
Теперь код скрипта, содержащийся в комментарии пользователя, превратится в безопасную форму еще на стадии сохранения в хранилище:
Однако во время рендера браузер покажет исходную форму кода без его выполнения:
С другой стороны, сохранение оригинальных пользовательских данных в серверное хранилище может оказаться более правильным решением. Например, если пользователь отправит текстовый комментарий с фрагментами кода:
То имеет смысл сохранить комментарий в исходном виде, а во время рендера выполнять фильтрацию.
Тем не менее, большей защиты от XSS можно добиться с помощью динамической генерации страниц, которая дает повышенный контроль над отображаемым контентом.
В этом случае сперва загружается базовая версия сайта (Single Page Application, SPA), а уже потом дополнительно загружается контент: новости, статьи, комментарии и т. п.
Во время вставки загруженных комментариев в отрисованную страницу вместо опасного innerHTML необходимо использовать безопасные textContent или innerText:
Таким образом код, явно сохраненный в тексте комментариев, будет показан в виде обычного текста без какого-либо выполнения.
Проблема DOM-base: межсайтовый скриптинг на уровне DOM отличается от межсайтового скриптинга с отражением только тем, что вставка вредоносного кода в разметку страницы выполняется без участия удаленного сервера — исключительно на локальном компьютере:
Злоумышленник может отправить жертве ссылку, содержащую вредоносный код в query-строке, — скрипт будет динамически вставлен в страницу и сразу же выполнен:
Решение для DOM-base: для предотвращения XSS на уровне DOM-дерева необходимо придерживаться некоторых правил во время динамической генерации страницы:
- Использование текстовой вставки. Вместо
innerHTMLприменятьtextContentилиinnerText. - Исключение функций выполнения кода. Не использовать
eval(),new Function(),setTimeout()и другие конструкции, напрямую выполняющие код. - Фильтрация и валидация данных. Проверять критические данные с помощью регулярных выражений.
Уязвимый код, показанный ранее, можно и нужно модифицировать:
Разумеется, приложениям со специфической логикой могут потребоваться менее общие и менее универсальные способы защиты.
Command-инъекция
Command-инъекция (Command injection) — разновидность уязвимости инъекции кода, позволяющая выполнять произвольные команды в операционной системе удаленного сервера.
Такие команды способны передать злоумышленнику полный контроль над удаленным сервером: изменение системных настроек, редактирование конфигурационных файлов, управление процессами, перезапуск физической машины — всё то, что может делать системный пользователь, от имени которого запущено серверное приложение.
Проблема: как и в остальных видах инъекций, уязвимый код использует пользовательские данные для выполнения системных команд:
Помимо имени файла, злоумышленник может передать дополнительные системные команды:
То есть адрес GET-запроса будет таким:
Дополнительная команда rm попытается удалить все каталоги корневой директории — то есть фактически всю систему целиком. Флаги -r и -f указывают на рекурсивное и принудительное удаление соответственно.
Решение: как и в остальных случаях, для предотвращения инъекции опасных команд необходима валидация пользовательских данных и более безопасный способ выполнения команд, в которых содержатся пользовательские данные:
- Валидация имени файла. Регулярные выражения проверяют наличие пробелов, слешей и других спецсимволов, которые используются внедрения вредоносных команд.
- Исполнение файла. Вместо функции exec используется
execFile, которая передает аргументы напрямую без запуска через shell, исключая возможность выполнения произвольной команды.
Соответственно, код выше модифицируется способами, похожими на защиту от других видов инъекций:
На самом деле существует множество других видов инъекций — мы показали лишь самые распространенные. Инъецировать можно всё что угодно — от кода и разметки до команд и шаблонов.
Уязвимость такого рода возможна в любом месте, где пользовательские данные тем или иным способом влияют на системную логику серверного приложения.
Поэтому любая внешняя (по отношению к серверу) информация требует тщательной валидации перед использованием. В противном случае она способна превратиться в инструмент похищения данных и управления сервером.
Межсайтовая подделка запроса (Cross-Site Request Forgery, CSRF)
Межсайтовая подделка запроса (Cross-Site Request Forgery, CSRF) — уязвимость, позволяющая принудить пользователя выполнить нежелательный запрос сайту, где он уже авторизован.
Алгоритм работы уязвимости выглядит примерно так:
- Когда пользователь заходит на определенные сайты (например, интернет-магазины, маркетплейсы или онлайн-банки), он авторизуется в личном кабинете и входит в свой профиль. В этот момент браузер сохраняет куки-файлы (cookies) на локальном компьютере, чтобы пользователь не вводил логин и пароль при каждом входе.
- Злоумышленник предлагает доверчивому пользователю перейти на произвольный сайт. На этом сайте может быть форма, которая либо автоматически (за счет скрипта), либо вручную (по клику пользователя) отправляет уязвимому сайту запрос на выполнение определенных действий, а браузер автоматически прикрепляет к этому запросу соответствующие куки.
- После обработки запроса, содержащего куки авторизации, уязвимый сайт выполняет те действия с профилем пользователя, которые от него требует злоумышленник. Пользователь же ничего не подозревает.
Как видно, CSRF не крадет что-либо (например, конфиденциальные данные) напрямую, а лишь выполняет определенные действия от имени пользователя. Однако, это не делает CSRF менее эффективным — такая уязвимость является самой распространенной.
Можно выделить несколько ключевых компонентов CSRF-атаки:
- Требуемое действие. На уязвимом сайте есть возможность выполнения действия от имени пользователя, которое приведет к результату, нужному злоумышленнику.
- Параметры запроса. Заранее известные параметры запроса, которые уязвимый сайт посчитает корректными для выполнения действия.
- Cookies-файлы. Куки-файлы, без который действие не может быть выполнено — даже если все параметры запроса указаны верно.
Таким образом, чтобы уязвимость CSRF сработала, необходимо наличие всех перечисленных компонентов.
Проблема:
Несмотря на проверку сессионных куки, этот код по прежнему имеет уязвимость CSRF. Пользователь может случайно выполнить запрос на изменение адреса доставки со стороннего сайта, браузер добавит сессионный куки, а код на удаленном сервере выполнит это требование без дополнительных проверок.
Решение: существует целый комплекс мер, позволяющих исключить уязвимость CSRF:
- CSRF-токенизация. Уникальная последовательность символов, которая отправляется сервером в момент инициации выполнения действия (например, во время отправки определенной формы). Именно этот токен должен вернуть пользователь во время выполнения действия.
- Same-Site Cookies. Специальный флаг, который разрешает использование куки только в рамках конкретного доменного имени.
- Подтверждение действий. Для некоторых действий имеет смысл внедрить механику дополнительного подтверждения. Например, нажимать кнопку, перемещать графический ползунок, вводить капчу.
Во-первых, необходимо добавить генерацию CSRF-токена в той форме, через которую пользователь должен самостоятельно менять адрес доставки:
Во-вторых, необходимо добавить проверку CSRF-токена в том месте, где выполняется смена адреса доставки:
Таким образом смена адреса доставки возможна только в том случае, если пользователь самостоятельно инициировал процесс изменения.
Broken Access Control
Нарушенный контроль доступа (Broken Access Control) — уязвимость, позволяющая выполнять запрещенные действия или получать приватные данные, обходя процесс проверки прав пользователя.
Уязвимости, связанные с контролем доступа, не настолько явны и формальны, как любые другие уязвимости. Они больше выглядят как недоработки, нежели как серьезные архитектурные недостатки — своего рода «опечатки» в логике приложения.
Зачастую в тех местах, где очевидна проверка прав доступа, она по невнимательности разработчика не выполняется. Таким образом появляется уязвимость.
Проблема: чаще всего уязвимые сайты предоставляют небезопасную прямую ссылку на объект — IDOR (Insecure Direct Object Reference):
В такой реализации, во-первых, отсутствует проверка авторизации пользователя по кукам, а во-вторых — не учитываются права доступа к документу.
Решение:
В код, показанный выше, необходимо добавить дополнительные проверки:
Чтобы не допускать возникновения уязвимости нарушенного контроля доступа, необходимо придерживаться некоторых правил при проектировании логики серверного приложения:
- Многоуровневые проверки. По критическим маршрутам и эндпоинтам выпполняется проверка аутентификации и прав пользователя.
- Запрет по умолчанию (Deny by Default). Открытый доступ есть только к тем ресурсам, к которым он действительно необходим.
- Минимизация прав (Least Privilege). У каждого пользователя есть только те роли и права, которые ему действительно необходимы. Необоснованные широкие права не предоставляются.
Говоря проще, важно не создавать переизбыток горизонтальных и вертикальных привилегий, а также проверять пользовательские права во всех критических местах приложения — везде, где пользователь читает данные и управляет функциями.
Подготовили для вас выгодные тарифы на облачные серверы
477 ₽/мес
657 ₽/мес
Заключение
Многим может показаться, что описанные уязвимости и показанные примеры уже не актуальны в мире современных приложений, протоколов, фреймворков и библиотек. Однако это не так.
Например, организация OWASP (Open Worldwide Application Security Project), включающая в себя множество корпораций, образовательных организаций и частных лиц со всего мира, в исследовании за 2021 год сообщает, что до 19% приложений в сети Интернет подвержены различным видам инъекции кода.
В другом отчете, подготовленном сервисом поиска уязвимостей Acunetix в 2020 году, 8% сайтов в Интернете были уязвимы к SQL-инъекциям, 25% — к Cross-Site Scripting (XSS) и 36% — к Cross-Site Request Forgery (CSRF).

Резюме отчета безопасности Acunetix за 2020 год
Настройка безопасности сайта по большей части заключается в выборе допустимых и недопустимых пользовательских данных, отправляемых как в сторону сервера, так и в сторону пользователя. Фильтрация и валидация — главный ключ к безопасности.
