Ваш код под прицелом: почему безопасность в Kotlin — не опция, а необходимость
Представьте: вы написали идеальное приложение. Красивый интерфейс, молниеносная скорость, чистая архитектура. Но в одной строчке кода прячется «чёрный ход». Злоумышленник вводит в поле поиска ' OR '1'='1 — и ваша база данных открыта как консервная банка. Знакомо? Если нет — отлично. Но лучше быть готовым заранее, чем тушить пожар в продакшене.
Kotlin — мощный и современный язык. Но, как и любой другой инструмент, он не делает ваш код безопасным по умолчанию. SQL-инъекции и XSS (Cross-Site Scripting) остаются в топе OWASP Top 10 уже много лет. И Kotlin-разработчики, которые думают «я использую ORM, мне ничего не грозит», часто оказываются в зоне риска.
В этой статье мы разберём, как превратить ваш Kotlin-код в неприступную крепость. Никакой воды — только практические советы, реальные примеры и лайфхаки, которые вы сможете применить уже сегодня.
SQL-инъекции в Kotlin: анатомия атаки и стратегии защиты
Как выглядит уязвимость (и почему это страшно)
SQL-инъекция возникает, когда пользовательский ввод попадает напрямую в SQL-запрос. Допустим, у вас есть такой код (никогда так не делайте!):
fun getUserById(id: String): User? {
val query = "SELECT * FROM users WHERE id = '$id'"
val statement = connection.createStatement()
val result = statement.executeQuery(query)
// ...
}
Если злоумышленник передаст id = "1'; DROP TABLE users; --", база данных выполнит: SELECT * FROM users WHERE id = '1'; DROP TABLE users; --'. И ваши пользователи исчезнут. Безвозвратно.
Правило №1: Всегда используйте параметризованные запросы
Это золотой стандарт. Kotlin (и Java) предоставляет PreparedStatement, который разделяет код SQL и данные:
fun getUserByIdSafe(id: String): User? {
val query = "SELECT * FROM users WHERE id = ?"
val preparedStatement = connection.prepareStatement(query)
preparedStatement.setString(1, id)
val result = preparedStatement.executeQuery()
// ...
}
База данных получает шаблон запроса отдельно от данных. Даже если в id прилетит вредоносный код, он будет интерпретирован как строка, а не как команда.
Правило №2: Используйте Exposed или другие ORM правильно
Библиотека Exposed — популярный выбор для Kotlin. Она безопасна, если использовать её API правильно. Плохой пример (сырая конкатенация):
// ПЛОХО: уязвимость
val users = transaction {
exec("SELECT * FROM users WHERE name = '$userInput'")
}
Хороший пример (использование DSL Exposed):
// ХОРОШО: экранирование встроено
val users = transaction {
Users.select { Users.name eq userInput }
}
В Exposed оператор eq автоматически экранирует значения. Но если вы пишете сырые SQL-запросы через exec — будьте бдительны.
Правило №3: Валидация на входе — ваш второй щит
Даже с параметризованными запросами не стоит пускать в базу всё подряд. Используйте встроенные средства Kotlin для проверки:
fun validateUserId(id: String): Boolean {
// Проверяем, что id — это UUID или целое число
return id.matches(Regex("^[a-fA-F0-9-]{36}$")) ||
id.all { it.isDigit() }
}
Помните: не доверяйте никакому вводу. Даже если он приходит из вашего же фронтенда. Особенно если он приходит из вашего же фронтенда.
XSS-атаки: как не дать JavaScript-коду захватить ваш сайт
Что такое XSS и почему Kotlin не защищает вас автоматически
XSS (Cross-Site Scripting) — это когда злоумышленник внедряет скрипт на вашу страницу. Например, в поле комментария он пишет: <script>alert('Ваши данные украдены')</script>. Если вы выведете этот текст без обработки, скрипт выполнится в браузере каждого пользователя, который откроет страницу.
Kotlin, как язык бэкенда (или мультиплатформы), не обрабатывает HTML-экранирование автоматически. Это ваша задача.
Борьба с XSS: экранирование и шаблонизаторы
Если вы используете Ktor (веб-фреймворк для Kotlin), не выводите пользовательские данные напрямую в HTML-шаблоны. Используйте экранирование:
// ПЛОХО: уязвимо для XSS
fun renderComment(comment: String): String {
return "<div>$comment</div>"
}
// ХОРОШО: экранируем HTML
fun renderCommentSafe(comment: String): String {
val escaped = comment
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'")
return "<div>$escaped</div>"
}
Но лучше использовать готовую библиотеку, например org.owasp.encoder:
// С OWASP Encoder
import org.owasp.encoder.Encode
fun renderCommentOWASP(comment: String): String {
return "<div>${Encode.forHtml(comment)}</div>"
}
Если вы работаете с шаблонизаторами (например, Kotlinx.html), они часто экранируют вывод по умолчанию. Но проверьте документацию — не все делают это корректно.
Защита от XSS в REST API: Content-Type и заголовки
Если ваше Kotlin-приложение — это REST API, XSS-атаки могут происходить через возвращаемые данные. Установите правильные заголовки:
// Ktor: защита от XSS через заголовки
install(DefaultHeaders) {
header("X-Content-Type-Options", "nosniff")
header("X-Frame-Options", "DENY")
header("Content-Security-Policy", "default-src 'self'")
}
А также всегда указывайте Content-Type: application/json для JSON-ответов. Это предотвратит MIME-сниффинг, когда браузер пытается выполнить JSON как HTML.
Лучшие практики безопасности Kotlin: от зависимостей до архитектуры
1. Держите зависимости в чистоте
Используйте плагины вроде owasp-dependency-check для Gradle. Они сканируют ваши зависимости на известные уязвимости (CVE). Запускайте проверку в CI/CD: