Безопасность Kotlin приложений: топ-10 уязвимостей и методы защиты
Kotlin — один из самых быстрорастущих языков программирования, используемый для разработки Android-приложений, серверных решений и кроссплатформенных проектов. Несмотря на встроенные механизмы безопасности (null safety, неизменяемость данных), разработчики часто допускают ошибки, которые приводят к серьезным уязвимостям. В этой статье мы разберем топ-10 уязвимостей Kotlin-приложений и покажем, как их избежать.
1. Неправильная обработка nullable-типов (Null Safety)
Одна из сильных сторон Kotlin — система nullable-типов. Однако разработчики часто злоупотребляют оператором !! (not-null assertion), что приводит к NullPointerException при любом неожиданном null-значении.
Пример уязвимости:
fun getUserName(user: User?): String {
return user!!.name // Если user = null, приложение упадет
}
Как исправить:
fun getUserName(user: User?): String {
return user?.name ?: "Гость" // Безопасный вызов с дефолтным значением
}
Рекомендация: Используйте ?.let, ?: (elvis) и safe casts. Избегайте !! в production-коде.
2. SQL-инъекции в серверных приложениях
При работе с базами данных через JDBC или Exposed разработчики иногда конкатенируют строки запросов, что открывает путь для SQL-инъекций.
Пример уязвимости:
val id = request.getParameter("id")
val query = "SELECT * FROM users WHERE id = $id" // Опасная конкатенация
Как исправить:
val id = request.getParameter("id")
val stmt = connection.prepareStatement("SELECT * FROM users WHERE id = ?")
stmt.setString(1, id) // Используем параметризованный запрос
Рекомендация: Всегда используйте PreparedStatement или ORM-библиотеки (Exposed, Hibernate) с параметризацией.
3. Небезопасное хранение данных (SharedPreferences)
В Android-приложениях часто хранят токены, пароли и другую конфиденциальную информацию в SharedPreferences в открытом виде.
Пример уязвимости:
val prefs = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
prefs.edit().putString("auth_token", "secret123").apply() // Данные в открытом виде
Как исправить:
// Используйте EncryptedSharedPreferences из AndroidX Security
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val prefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
prefs.edit().putString("auth_token", "secret123").apply()
Рекомендация: Для хранения чувствительных данных используйте EncryptedSharedPreferences или KeyStore.
4. Утечка данных через логи (Logging)
Разработчики часто оставляют в коде логи, которые выводят пароли, токены или персональные данные пользователей.
Пример уязвимости:
Log.d("Auth", "User password: $password") // Пароль попадает в логи
Как исправить:
// Используйте Timber или кастомный логгер с фильтрацией
Log.d("Auth", "User authenticated successfully") // Не логируем чувствительные данные
Рекомендация: Включите ProGuard/R8 для удаления логов в release-сборке и используйте библиотеки с маскировкой данных (например, Timber).
5. Небезопасные WebView (Android)
WebView — частая точка входа для XSS-атак и утечки данных, если включен JavaScript и не настроены фильтры.
Пример уязвимости:
webView.settings.javaScriptEnabled = true
webView.loadUrl("file:///android_asset/malicious.html") // Уязвимость к XSS
Как исправить:
webView.settings.javaScriptEnabled = false // Отключаем JS, если не нужен
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean {
// Проверяем URL на белый список
return !isAllowedUrl(request.url.toString())
}
}
Рекомендация: Отключайте JavaScript, если он не требуется, и используйте белый список разрешенных URL.
6. Отсутствие проверки прав доступа (Android Permissions)
Запрос разрешений на доступ к камере, контактам или местоположению без проверки может привести к утечке данных.
Пример уязвимости:
// Запрос разрешения без проверки, предоставлено ли оно
val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(cameraIntent, REQUEST_CODE)
Как исправить:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED) {
// Только после проверки запускаем камеру
val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
startActivityForResult(cameraIntent, REQUEST_CODE)
} else {
// Запрашиваем разрешение
ActivityCompat.requestPermissions(this,
arrayOf(Manifest.permission.CAMERA), REQUEST_CODE)
}
Рекомендация: Всегда проверяйте разрешения перед доступом к конфиденциальным API.
7. Небезопасное шифрование (Crypto)
Использование устаревших алгоритмов (MD5, SHA1) или неправильная генерация ключей делает шифрование бесполезным.
Пример уязвимости:
val hash = MessageDigest.getInstance("MD5").digest(password.toByteArray()) // MD5 устарел
Как исправить:
val hash = MessageDigest.getInstance("SHA-256").digest(password.toByteArray())
// Или используйте bcrypt/argon2 для хеши