Безопасность Kotlin приложений: топ-10 уязвимостей

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение

Kotlin — современный язык программирования, который активно используется для разработки Android-приложений, серверных решений (Ktor, Spring Boot) и кросс-платформенных проектов. Несмотря на его строгую типизацию и null-безопасность, разработчики часто допускают ошибки, приводящие к серьёзным уязвимостям. В этой статье мы разберём топ-10 уязвимостей Kotlin-приложений, основанных на стандартах OWASP, и покажем, как их избежать.

Статья будет полезна как начинающим, так и опытным Kotlin-разработчикам, желающим повысить уровень безопасности своих проектов.

1. SQL-инъекции (SQL Injection)

Проблема: Внедрение вредоносного SQL-кода через пользовательский ввод.

Пример уязвимого кода:

// Уязвимо! Не используйте конкатенацию строк
fun getUserById(id: String): User? {
    val query = "SELECT * FROM users WHERE id = '$id'"
    val result = connection.createStatement().executeQuery(query)
    return result.toUser()
}

Решение: Используйте параметризованные запросы (PreparedStatement).

// Безопасно
fun getUserById(id: String): User? {
    val query = "SELECT * FROM users WHERE id = ?"
    val preparedStatement = connection.prepareStatement(query)
    preparedStatement.setString(1, id)
    val result = preparedStatement.executeQuery()
    return result.toUser()
}

2. Межсайтовый скриптинг (XSS)

Проблема: Вставка вредоносного JavaScript-кода в веб-страницу.

Пример уязвимого кода (Ktor):

// Уязвимо! Не экранируйте вывод
get("/search") {
    val query = call.parameters["q"] ?: ""
    call.respondHtml {
        h1 { +"Результаты поиска для: $query" } // XSS!
    }
}

Решение: Используйте экранирование HTML (например, kotlinx.html с экранированием).

// Безопасно
import kotlinx.html.escapeHtml

get("/search") {
    val query = call.parameters["q"] ?: ""
    val safeQuery = query.escapeHtml()
    call.respondHtml {
        h1 { +"Результаты поиска для: $safeQuery" }
    }
}

3. Небезопасное хранение паролей

Проблема: Хранение паролей в открытом виде или с использованием слабых хэшей (MD5, SHA-1).

Пример уязвимого кода:

// Уязвимо! MD5 — не подходит для паролей
fun saveUser(password: String) {
    val hash = MessageDigest.getInstance("MD5").digest(password.toByteArray())
    // сохраняем hash в БД
}

Решение: Используйте bcrypt, scrypt или Argon2.

// Безопасно (с использованием библиотеки jbcrypt)
import org.mindrot.jbcrypt.BCrypt

fun hashPassword(password: String): String {
    return BCrypt.hashpw(password, BCrypt.gensalt(12))
}

fun checkPassword(password: String, hashed: String): Boolean {
    return BCrypt.checkpw(password, hashed)
}

4. Утечка конфиденциальных данных через логи

Проблема: Логирование паролей, токенов, номеров карт.

Пример уязвимого кода:

// Уязвимо! Логируем чувствительные данные
fun login(username: String, password: String) {
    log.info("Login attempt: username=$username, password=$password")
}

Решение: Используйте фильтры или маскирование.

// Безопасно
fun login(username: String, password: String) {
    log.info("Login attempt: username=$username")
    // password не логируем
}

5. Небезопасная десериализация

Проблема: Выполнение произвольного кода при десериализации непроверенных данных.

Пример уязвимого кода:

// Уязвимо! Десериализация без проверки
fun processData(data: ByteArray): Any {
    val ois = ObjectInputStream(ByteArrayInputStream(data))
    return ois.readObject() // может быть зловредным
}

Решение: Используйте безопасные форматы (JSON, Protocol Buffers) и проверяйте типы.

// Безопасно (используем kotlinx.serialization)
@Serializable
data class MyData(val name: String, val age: Int)

fun processData(json: String): MyData {
    return Json.decodeFromString<MyData>(json)
}

6. Недостатки аутентификации и управления сессиями

Проблема: Слабые сессионные токены, отсутствие инвалидации сессий, хранение токенов в URL.

Пример уязвимого кода:

// Уязвимо! Токен в URL
val url = "https://api.example.com/user?sessionId=$sessionId"

Решение: Используйте безопасные HTTP-Only куки и генерируйте криптостойкие токены.

// Безопасно (используем SecureRandom)
import java.security.SecureRandom

fun generateSessionToken(): String {
    val random = SecureRandom()
    val bytes = ByteArray(32)
    random.nextBytes(bytes)
    return bytes.joinToString("") { "%02x".format(it) }
}

7. Переполнение буфера и целочисленное переполнение

Проблема: Kotlin работает на JVM, но нативные библиотеки (через JNI) могут быть уязвимы.

Пример уязвимого кода (JNI):

// Уязвимо! Нет проверки размера
fun processBuffer(data: ByteArray) {
    val nativeBuffer = malloc(data.size) // может быть переполнение
    // копирование данных
}

Решение: Всегда проверяйте границы массивов и используйте безопасные функции.

// Безопасно
fun safeProcessBuffer(data: ByteArray) {
    require(data.size < MAX_BUFFER_SIZE) { "Buffer too large" }
    val nativeBuffer = malloc(data.size) ?: throw OutOfMemoryError()
}

8. Небезопасное использование Reflection

Блоги

Рекомендаций по книгам