Введение: почему безопасность Swift-приложений критически важна
Swift — мощный и современный язык программирования, созданный Apple для разработки приложений под iOS, macOS, watchOS и tvOS. Несмотря на встроенные механизмы безопасности (ARC, безопасная работа с памятью, опциональные типы), тысячи приложений ежегодно становятся жертвами атак из-за ошибок разработчиков. Согласно отчету OWASP Mobile Top 10, большинство уязвимостей в мобильных приложениях возникают на стороне клиента. В этой статье мы разберем 10 наиболее распространенных уязвимостей Swift-приложений, приведем примеры кода и покажем, как их избежать.
1. Небезопасное хранение данных (Insecure Data Storage)
Проблема
Разработчики часто сохраняют конфиденциальные данные (токены, пароли, ключи API) в UserDefaults, Plist-файлах или Core Data без шифрования. На джейлбрейкнутых устройствах злоумышленник может легко получить к ним доступ.
Пример уязвимого кода
// Уязвимый код
UserDefaults.standard.set("secret_token_123", forKey: "api_token")
UserDefaults.standard.set("password123", forKey: "user_password")
Решение
Используйте Keychain для хранения чувствительных данных. Swift имеет удобные обертки, такие как KeychainSwift или Security framework.
// Безопасное хранение с Keychain
import KeychainSwift
let keychain = KeychainSwift()
keychain.set("secret_token_123", forKey: "api_token")
keychain.set("password123", forKey: "user_password", withAccess: .accessibleWhenUnlockedThisDeviceOnly)
2. Инъекция кода и SQL-инъекции (Code Injection & SQL Injection)
Проблема
Если ваше приложение использует SQLite, Core Data с NSPredicate или WebView для отображения веб-контента, злоумышленник может внедрить вредоносный код через пользовательский ввод.
Пример уязвимого кода
// Уязвимый запрос к SQLite
let userInput = "1' OR '1'='1"
let query = "SELECT * FROM users WHERE id = '\(userInput)'"
Решение
Используйте параметризованные запросы или NSPredicate с безопасными подстановками.
// Безопасный запрос с параметрами
let query = "SELECT * FROM users WHERE id = ?"
let statement = try db.prepare(query)
try statement.bind(1, userInput)
3. Незащищенная сетевая коммуникация (Insecure Network Communication)
Проблема
Использование HTTP вместо HTTPS, игнорирование SSL-сертификатов (App Transport Security) или отправка данных без шифрования.
Пример уязвимого кода
// Уязвимый код: отключение ATS
let url = URL(string: "http://example.com/api/login")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
// Обработка незащищенных данных
}
Решение
Настройте App Transport Security (ATS) в Info.plist и используйте HTTPS. Для проверки сертификатов применяйте SSL pinning.
// Безопасный запрос с SSL Pinning (пример с Alamofire)
let evaluator = PinnedCertificatesTrustEvaluator(certificates: [certificate])
let session = Session(serverTrustManager: ServerTrustManager(evaluators: ["example.com": evaluator]))
session.request("https://example.com/api/login").responseJSON { response in
// Обработка защищенных данных
}
4. Инъекция через WebView (WebView Injection)
Проблема
Загрузка HTML-контента с пользовательскими данными в WKWebView без санитизации может привести к XSS-атакам и краже cookies.
Пример уязвимого кода
// Уязвимый код
let userComment = "<script>alert('XSS')</script>"
let html = "<html><body>\(userComment)</body></html>"
webView.loadHTMLString(html, baseURL: nil)
Решение
Экранируйте HTML-символы и используйте WKUserContentController для безопасной коммуникации.
// Безопасное экранирование
extension String {
func escapeHTML() -> String {
return self
.replacingOccurrences(of: "&", with: "&")
.replacingOccurrences(of: "<", with: "<")
.replacingOccurrences(of: ">", with: ">")
.replacingOccurrences(of: "\"", with: """)
}
}
let safeComment = userComment.escapeHTML()
let html = "<html><body>\(safeComment)</body></html>"
5. Недостаточная защита от обратной разработки (Reverse Engineering)
Проблема
Swift-код компилируется в бинарный файл Mach-O, который можно дизассемблировать с помощью инструментов (Hopper, IDA Pro, Ghidra). Злоумышленник может извлечь строки, ключи шифрования и логику приложения.
Пример уязвимого кода
// Уязвимый код: хранение ключа в строке
let apiKey = "sk_live_1234567890abcdef"
Решение
Используйте обфускацию строк, шифрование и защиту от отладки. Также применяйте ptrace для обнаружения отладчика.
// Обфускация ключа
let apiKey = String(data: Data(base64Encoded: "c2tfbGl2ZV8xMjM0NTY3ODkwYWJjZGVm")!, encoding: .utf8)!
// Проверка на джейлбрейк
func isJailbroken() -> Bool {
let paths = ["/Applications/Cydia.app", "/Library/MobileSubstrate/MobileSubstrate.dylib"]
return paths.contains { FileManager.default.fileExists(atPath: