Как не дать хакерам взломать ваш Java-код: SQL-инъекции и XSS под микроскопом
Представьте: вы написали идеальное веб-приложение на Java. Красивый интерфейс, быстрая логика, чистая архитектура. Вы выкладываете его на сервер, завариваете чай и... через час получаете сообщение, что база данных удалена, а на главной странице красуется чей-то дерзкий ASCII-арт. Знакомая история? К сожалению, для многих разработчиков — да.
Две самые распространённые и опасные уязвимости веб-приложений — SQL-инъекции и межсайтовый скриптинг (XSS) — ежегодно лишают сна администраторов тысяч сайтов. И Java, несмотря на свою строгость, не является исключением. Но есть и хорошая новость: защититься от них реально. И даже не очень сложно.
В этой статье мы разберём, как именно злоумышленники атакуют Java-приложения, и главное — как поставить надёжный блокпост с помощью проверенных инструментов и правильных привычек кодирования. Пристегните ремни: будет много кода, лайфхаков и минимум воды.
SQL-инъекции: когда пользователь становится программистом
Что это такое и почему это работает?
SQL-инъекция — это техника атаки, при которой злоумышленник «впрыскивает» вредоносный SQL-код в запрос через пользовательский ввод. Если приложение бездумно склеивает строки, атакующий может получить доступ к чужим данным, удалить таблицы или даже захватить управление над сервером базы данных.
Классический пример плохого кода на Java:
String userInput = request.getParameter("username");
String query = "SELECT * FROM users WHERE username = '" + userInput + "'";
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query);
Что происходит, если пользователь вводит: admin' OR '1'='1? Запрос превращается в:
SELECT * FROM users WHERE username = 'admin' OR '1'='1'
Условие '1'='1' всегда истинно. Поздравляю: злоумышленник только что вошёл в систему как администратор, даже не зная пароля. А если он введёт '; DROP TABLE users; -- — пиши пропало.
Как защититься: PreparedStatement — ваш щит
Правило номер один в безопасности Java-приложений: никогда не конкатенируйте пользовательские данные с SQL-запросами. Используйте параметризованные запросы через PreparedStatement.
Вот как должен выглядеть безопасный код:
String userInput = request.getParameter("username");
String query = "SELECT * FROM users WHERE username = ?";
PreparedStatement pstmt = connection.prepareStatement(query);
pstmt.setString(1, userInput);
ResultSet rs = pstmt.executeQuery();
Что здесь произошло? База данных получает запрос и данные отдельно. Даже если userInput содержит зловредный SQL-код, он будет воспринят как обычная строка, а не как команда. PreparedStatement автоматически экранирует спецсимволы. Это первая и самая важная линия обороны.
Дополнительные меры: хранимые процедуры и валидация
Помимо PreparedStatement, стоит использовать:
- Хранимые процедуры — вызов процедуры через
CallableStatementтакже отделяет код от данных. - Белые списки — если нужно подставить имя таблицы или столбца (что бывает редко), никогда не берите его из ввода пользователя. Используйте enum или строго заданные константы.
- Валидация ввода — проверяйте, что email действительно похож на email, а число — это число. Но помните: валидация — это дополнительный слой, а не замена
PreparedStatement.
Пример безопасной работы с динамическими именами таблиц:
public enum TableName {
USERS, PRODUCTS, ORDERS
}
public void queryTable(TableName table) {
String query = "SELECT * FROM " + table.name().toLowerCase();
// Используйте Statement только если имя таблицы строго из enum!
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query);
}
Этот подход исключает возможность инъекции, так как значение берётся из ограниченного набора констант.
XSS-атаки: когда ваш сайт атакует сам себя
Тихая угроза на каждой странице
Межсайтовый скриптинг (Cross-Site Scripting, XSS) — это уязвимость, при которой злоумышленник внедряет в веб-страницу вредоносный JavaScript-код. Этот код выполняется в браузере жертвы, крадёт куки, перенаправляет на фишинговые сайты или портит интерфейс.
Различают три основных типа XSS:
- Отражённый (Reflected) — вредоносный код передаётся через URL или форму и сразу отображается на странице.
- Хранимый (Stored) — код сохраняется на сервере (в БД, файле) и отображается всем посетителям (например, в комментариях).
- DOM-based — уязвимость на стороне клиента, когда JavaScript манипулирует DOM-деревом на основе непроверенных данных.
Пример уязвимого кода на Java (JSP или сервлет):
String comment = request.getParameter("comment");
out.println("<p>Ваш комментарий: " + comment + "</p>");
Если пользователь отправит: <script>alert('XSS')</script>, то каждый, кто откроет эту страницу, увидит всплывающее окно. А если вместо alert будет код, отправляющий куки на сервер злоумышленника? Последствия могут быть катастрофическими.
Экранирование вывода: главный принцип защиты от XSS
Основное правило защиты от XSS в Java: никогда не доверяйте данным, которые пришли от пользователя. Любой вывод, содержащий пользовательские данные, должен быть экранирован.
Используйте библиотеки для экранирования HTML. Самые популярные:
- OWASP Java HTML Sanitizer — мощный инструмент для очистки HTML.
- Apache Commons Text — содержит утилиты для экранирования.
- Spring Framework — встроенные механизмы в JSP (JSTL) и Thymeleaf.
Пример с Apache Commons Text:
import org.apache.commons.text.StringEscapeUtils;
String userInput = request.getParameter("comment");
String safeComment = StringEscapeUtils.escapeHtml4(userInput);
out.println("<p>Ваш