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

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

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

Начать курс

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

Разработка мобильных приложений под iOS и macOS требует особого внимания к безопасности. Swift — мощный и современный язык, но даже он не защищает разработчика от типовых ошибок, которые могут привести к утечке данных, взлому или финансовым потерям. В этой статье мы разберем топ-10 уязвимостей Swift-приложений с реальными примерами кода и практическими рекомендациями по их устранению.

1. Инъекции SQL через Core Data и SQLite

Многие разработчики используют сырые SQL-запросы в паре с SQLite. Если данные от пользователя не экранируются, злоумышленник может выполнить произвольный запрос к базе.

// ❌ Уязвимый код
let query = "SELECT * FROM users WHERE name = '\(userInput)'"

// ✅ Безопасный код (использование параметризованных запросов)
let query = "SELECT * FROM users WHERE name = ?"
var statement: OpaquePointer?
sqlite3_prepare_v2(database, query, -1, &statement, nil)
sqlite3_bind_text(statement, 1, (userInput as NSString).utf8String, -1, nil)

Используйте параметризованные запросы или Core Data с предикатами — это исключает инъекции.

2. Небезопасное хранение данных в Keychain

Keychain — безопасное место, но многие разработчики неправильно настраивают атрибуты доступа. Например, если не указать kSecAttrAccessible, данные могут быть доступны после резервного копирования.

// ❌ Уязвимый код (доступно после бэкапа)
let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: "token",
    kSecValueData as String: tokenData
]

// ✅ Безопасный код (только когда устройство разблокировано)
let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: "token",
    kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
    kSecValueData as String: tokenData
]

3. Отсутствие проверки SSL/TLS (Man-in-the-Middle)

При разработке часто отключают проверку сертификатов для тестирования, но забывают вернуть настройки в релиз. Это позволяет атакующему перехватывать трафик.

// ❌ Уязвимый код (опасно для продакшена)
let session = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
// В делегате:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
}

// ✅ Безопасный код — используйте стандартную проверку
let session = URLSession.shared
// Или используйте ATS (App Transport Security) в Info.plist

Включите App Transport Security (ATS) и не отключайте его в релизной сборке.

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

Разработчики часто логируют чувствительные данные (токены, пароли) для отладки. Если лог попадёт в сторонний сервис (например, Firebase Crashlytics), данные утекут.

// ❌ Уязвимый код
print("User token: \(userToken)")

// ✅ Безопасный код
#if DEBUG
    print("User token: [REDACTED]")
#endif
// Или используйте os_log с уровнем приватности
os_log("User token: %{private}@", log: .default, type: .debug, userToken)

Никогда не логируйте чувствительные данные в продакшене. Используйте маскирование или условную компиляцию.

5. Небезопасное хранение секретов в коде

Хранение API-ключей, токенов и паролей прямо в исходном коде — грубейшая ошибка. Даже если код обфусцирован, строки легко извлечь из бинарного файла.

// ❌ Уязвимый код
let apiKey = "sk-1234567890abcdef"

// ✅ Безопасный код — используйте серверную аутентификацию
// Или шифруйте ключи с помощью CryptoKit
import CryptoKit
let key = SymmetricKey(data: "server_secret".data(using: .utf8)!)
let sealedBox = try! AES.GCM.seal(apiKeyData, using: key)

Лучший подход — получать секреты с сервера после аутентификации пользователя.

6. Уязвимости в WebView (JavaScript Injection)

Если ваше приложение использует WKWebView и загружает в него HTML с пользовательским контентом, злоумышленник может выполнить произвольный JavaScript.

// ❌ Уязвимый код
let html = "<html><body>\(userComment)</body></html>"
webView.loadHTMLString(html, baseURL: nil)

// ✅ Безопасный код — экранируйте HTML
import Foundation
extension String {
    func escapedForHTML() -> String {
        // Замена спецсимволов
        return self.replacingOccurrences(of: "&", with: "&")
                   .replacingOccurrences(of: "<", with: "<")
                   // ... другие замены
    }
}
webView.loadHTMLString("<html><body>\(userComment.escapedForHTML())</body></html>", baseURL: nil)

Отключите JavaScript в WebView, если он не нужен: webView.configuration.preferences.javaScriptEnabled = false.

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

Использование NSCoding или JSONDecoder с непроверенными данными может привести к выполнению произвольного кода.

// ❌ Уязвимый код (NSCoding с внешними данными)
let data = Data(base64Encoded: userInput)!
let object = try! NSKeyedUnarchiver.unarchivedObject(ofClasses: [MyClass.self], from: data)

// ✅ Безопасный код — проверяйте типы и используйте Codable
struct UserData: Codable {
    let name: String
    let age: Int
}
do {
    let user = try JSONDecoder().decode(UserData.self, from: data)
} catch {
    // Обработка ошибки
}

Избегайте NSCoding для данных из ненадёжных источников. Используйте Codable с чёткой структур

Блоги

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