1. Внедрение кода: SQL-инъекции и инъекции команд в Java-приложениях
Внедрение кода: SQL-инъекции и инъекции команд в Java-приложениях
Добро пожаловать на курс «Топ-10 уязвимостей безопасности в Java: теория и практика». Мы начинаем наше погружение в мир кибербезопасности с одной из самых старых, но всё ещё разрушительных категорий уязвимостей — инъекций (Injection).
Даже если вы опытный Java-разработчик, вы наверняка слышали термин «SQL-инъекция». Однако понимание механики этого процесса на уровне байт-кода и протоколов часто остаётся поверхностным. Сегодня мы разберем, как именно злоумышленники заставляют ваши приложения выполнять код, который вы не писали, и, самое главное, как защитить свой код от подобных атак.
Что такое инъекция?
Инъекция происходит, когда ненадежные данные (ввод пользователя) отправляются интерпретатору как часть команды или запроса. Проблема заключается в том, что интерпретатор (будь то база данных, командная оболочка ОС или парсер XML) не может отличить данные от исполняемого кода.
В Java-мире наиболее распространены два типа инъекций:
Разберем их детально.
SQL-инъекции (SQL Injection)
SQL-инъекция возникает, когда разработчик создает SQL-запрос путем простой конкатенации (склеивания) строк, включая в запрос данные, полученные от пользователя, без предварительной проверки или экранирования.
Анатомия уязвимости
Представьте, что у вас есть простой метод аутентификации, использующий JDBC:
На первый взгляд код кажется логичным. Если пользователь введет admin, запрос будет выглядеть так:
SELECT * FROM users WHERE name = 'admin'
Однако, что если злоумышленник введет в поле username следующую строку:
admin' OR '1'='1
В результате конкатенации итоговый SQL-запрос, отправленный в базу данных, примет вид:
SELECT * FROM users WHERE name = 'admin' OR '1'='1'
Математическая логика атаки
Давайте посмотрим на это с точки зрения булевой алгебры. Условие WHERE теперь состоит из двух частей, объединенных оператором ИЛИ (OR).
Запишем это в виде формулы:
где — это условие проверки имени пользователя (name = 'admin'), — это внедренное условие ('1'='1'), а — логический оператор ИЛИ.
В логике существует правило: если хотя бы один из операндов в операции ИЛИ истинен, то и все выражение истинно. Так как выражение '1'='1' всегда истинно (является тавтологией), то и все условие WHERE становится истинным для каждой строки в таблице users. В результате база данных вернет первую попавшуюся запись (часто это запись администратора), и злоумышленник войдет в систему без пароля.
Последствия SQLi
* Обход аутентификации: Вход под чужим аккаунтом.
* Утечка данных: Использование оператора UNION для извлечения данных из других таблиц.
* Потеря данных: Злоумышленник может выполнить DROP TABLE или DELETE.
Защита: Prepared Statements
Единственный надежный способ защиты от SQL-инъекций в Java — использование параметризованных запросов (Prepared Statements).
Вместо того чтобы вклеивать данные в строку запроса, мы используем плейсхолдеры (знаки вопроса ?).
Почему это работает?
Когда вы используете PreparedStatement, база данных сначала компилирует структуру SQL-запроса, и только потом подставляет данные. База данных «знает», что то, что придет вместо ?, — это просто данные, строка текста, а не исполняемая команда SQL. Даже если отправить admin' OR '1'='1, база будет искать пользователя с буквальным именем «admin' OR '1'='1».
Инъекции команд (Command Injection)
Инъекция команд (или OS Command Injection) происходит, когда приложение передает небезопасные данные пользователя в системную оболочку (shell) для выполнения команд операционной системы.
В Java это часто случается при использовании классов Runtime или ProcessBuilder.
Пример уязвимого кода
Допустим, вы пишете утилиту для администраторов, которая проверяет доступность сервера через команду ping.
Разработчик ожидает, что в address придет IP-адрес, например 8.8.8.8.
Но злоумышленник может передать такую строку:
8.8.8.8; rm -rf /
(В Windows аналогом разделителя ; может быть & или |).
В результате операционная система получит команду:
ping -c 3 8.8.8.8; rm -rf /
Сначала выполнится ping, а сразу после него — команда удаления файлов rm -rf /. Если приложение запущено с правами root (что само по себе плохая практика), последствия будут катастрофическими.
Защита от Command Injection
ls используйте java.nio.file.Files.ProcessBuilder с разделением аргументов. Не передавайте всю команду одной строкой.Безопасный вариант:
В этом случае ProcessBuilder не запускает оболочку (shell) для интерпретации строки. Он вызывает исполняемый файл ping и передает ему address как один аргумент. Даже если в address будут спецсимволы ; или &&, программа ping просто попытается найти хост с таким странным именем и выдаст ошибку «Unknown host», но вторая команда не выполнится.
Общие принципы защиты
Независимо от типа инъекции, следуйте этим правилам:
* Никогда не доверяйте вводу пользователя. Любые данные извне (формы, заголовки HTTP, файлы) потенциально опасны.
* Разделяйте данные и код. Это главный принцип защиты от инъекций.
* Принцип наименьших привилегий. Приложение должно работать с минимально необходимыми правами в базе данных и ОС. Если приложению нужно только читать данные, у него не должно быть прав на DROP TABLE.
> «Безопасность — это не продукт, а процесс.» — Брюс Шнайер
В следующей статье мы рассмотрим Cross-Site Scripting (XSS) — ситуацию, когда инъекция происходит не на сервере, а в браузере пользователя, и узнаем, почему JavaScript может быть опаснее, чем кажется.