Безопасность Kotlin приложений: топ-10 уязвимостей для начинающих
Kotlin — современный язык программирования, который активно используется для разработки Android-приложений, серверных решений (Ktor, Spring Boot) и даже фронтенда (Kotlin/JS). Однако, как и любой другой язык, Kotlin подвержен уязвимостям, особенно если разработчик только начинает свой путь. В этой статье мы разберем топ-10 уязвимостей Kotlin-приложений, которые должен знать каждый начинающий разработчик. Мы рассмотрим примеры кода и дадим практические советы по их устранению.
1. Игнорирование Null Safety
Kotlin славится своей безопасной работой с null-значениями (Null Safety). Однако новички часто используют оператор !! (not-null assertion), что приводит к NullPointerException.
// Плохо: риск NPE
val name: String? = null
println(name!!.length)
// Хорошо: безопасный вызов
println(name?.length ?: "Имя отсутствует")
Рекомендация: Избегайте !!. Используйте элвис-оператор (?:) или let/also.
2. Небезопасное хранение секретов (Hardcoded Secrets)
Хранение API-ключей, токенов и паролей прямо в коде — грубая ошибка. Злоумышленник может легко извлечь их из APK-файла.
// Плохо: токен в коде
val apiToken = "sk-1234567890abcdef"
// Хорошо: использование BuildConfig или NDK
// В build.gradle: buildConfigField "String", "API_TOKEN", "\"${apiToken}\""
Рекомендация: Используйте переменные окружения, BuildConfig или безопасное хранилище (Android Keystore).
3. SQL-инъекции в Android (Room/Exposed)
Даже с ORM (Object-Relational Mapping) можно допустить инъекцию, если использовать сырые SQL-запросы с конкатенацией строк.
// Плохо: уязвимость к инъекции
val query = "SELECT * FROM users WHERE name = '$userInput'"
// Хорошо: параметризованный запрос
val query = "SELECT * FROM users WHERE name = ?"
val cursor = db.rawQuery(query, arrayOf(userInput))
Рекомендация: Всегда используйте параметризованные запросы или безопасные API (Room @Query с параметрами).
4. Небезопасное логирование
Логирование чувствительных данных (пароли, номера карт) в продакшене может привести к утечке информации.
// Плохо: логирование пароля
Log.d("Auth", "User password: $password")
// Хорошо: логирование только факта
Log.d("Auth", "User authentication attempt")
Рекомендация: Используйте уровни логирования и фильтры. В продакшене отключите Log.v и Log.d.
5. Неправильное использование SharedPreferences
SharedPreferences по умолчанию хранят данные в XML-файле без шифрования. Если устройство рутировано, данные доступны злоумышленнику.
// Плохо: обычные SharedPreferences
val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
// Хорошо: EncryptedSharedPreferences (AndroidX Security)
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val encryptedPrefs = EncryptedSharedPreferences.create(
context, "secure_prefs", masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
Рекомендация: Для конфиденциальных данных используйте EncryptedSharedPreferences или DataStore с шифрованием.
6. Уязвимости в десериализации (JSON/Gson/Moshi)
При десериализации недоверенных данных можно получить выполнение произвольного кода или утечку памяти.
// Плохо: использование Gson без валидации
val user = Gson().fromJson(jsonString, User::class.java)
// Хорошо: валидация полей
data class User(
@SerializedName("id")
val id: Int,
@SerializedName("name")
val name: String
) {
init {
require(id > 0) { "Invalid user ID" }
}
}
Рекомендация: Всегда валидируйте поля после десериализации. Используйте Kotlinx.serialization с безопасными типами.
7. Небезопасная работа с WebView
WebView может выполнять JavaScript из недоверенных источников, что ведет к XSS-атакам.
// Плохо: включен JavaScript для всех URL
webView.settings.javaScriptEnabled = true
webView.loadUrl("http://example.com")
// Хорошо: ограничение доступа
webView.settings.javaScriptEnabled = false
// Или используйте WebViewClient с проверкой URL
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
return request?.url?.host != "trusted.example.com"
}
}
Рекомендация: Отключайте JavaScript, если он не нужен. Используйте белые списки URL.
8. Отсутствие проверки разрешений (Permissions)
Запрос разрешений без проверки их статуса может привести к крашу приложения или утечке данных.
// Плохо: вызов без проверки
camera.takePicture()
// Хорошо: проверка разрешения
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
camera.takePicture()
} else {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQUEST_CODE)
}
Рекомендация: Всегда проверяйте разрешения перед выполнением чувствительных операций.
9. Небезопасная работа с файлами (Path Traversal)
Если вы сохраняете файлы на основе пользовательского ввода, злоумышленник может записать файл в другую директорию (например, ../../system).
// Плохо: пользовательский ввод в имени файла
val file