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

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

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

Начать курс

Безопасность 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                

Блоги

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