설명
UserDefaults는 앱 설정과 작은 사용자 기본값을 저장하기 위한 API입니다. 인증 토큰, 세션 ID, API 키, 비밀번호 같은 민감 값을 UserDefaults 또는 NSUbiquitousKeyValueStore에 평문으로 저장하면 기기 백업, 디버그 추출, 탈옥된 기기, iCloud 키-값 저장소 동기화 경로를 통해 값이 노출될 수 있습니다.
잠재적 영향
- 인증 토큰 또는 세션 탈취
- API 키와 클라이언트 비밀값 노출
- iCloud 동기화 또는 백업 경로를 통한 민감 정보 확산
- 탈취한 값 재사용을 통한 계정 접근
해결 방법
- 민감한 값은 가능한 한 기기에 저장하지 않습니다.
- 저장이 필요하면 Keychain Services 또는 별도 키 관리가 적용된 검증된 암호화 저장소를 사용합니다. Secure Enclave는 일반 비밀값의 보관소가 아니라 지원되는 암호화 키의 보호에 사용합니다.
UserDefaults에는 테마, 언어, 온보딩 여부 같은 비민감 설정만 저장합니다.NSUbiquitousKeyValueStore에는 iCloud 동기화가 필요한 비민감 설정만 저장합니다.
예시
변경 전
swift
func saveToken(token: String) {
UserDefaults.standard.set(token, forKey: "access_token")
}
변경 후
swift
import Foundation
import Security
enum KeychainStorageError: Error {
case unexpectedStatus(OSStatus)
}
func saveToken(token: Data) throws {
let item: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app.auth",
kSecAttrAccount as String: "access_token",
kSecAttrAccessible as String:
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecValueData as String: token
]
let addStatus = SecItemAdd(item as CFDictionary, nil)
if addStatus == errSecSuccess { return }
guard addStatus == errSecDuplicateItem else {
throw KeychainStorageError.unexpectedStatus(addStatus)
}
let match: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: "com.example.app.auth",
kSecAttrAccount as String: "access_token"
]
let updates: [String: Any] = [
kSecAttrAccessible as String:
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
kSecValueData as String: token
]
let updateStatus = SecItemUpdate(
match as CFDictionary, updates as CFDictionary)
guard updateStatus == errSecSuccess else {
throw KeychainStorageError.unexpectedStatus(updateStatus)
}
}
설명:
- 변경 전:
UserDefaults에 인증 토큰을 평문으로 저장하면 앱 샌드박스 추출, 백업, 디버그 환경을 통해 값이 노출될 수 있습니다. - 변경 후: 민감 값은 Keychain Services에 기기·패스코드 전용 접근성으로 저장합니다. 추가 결과를 확인하고 기존 항목이면 값과 접근성을 명시적으로 갱신하며, 다른 오류에서는 더 약한 저장소로 폴백하지 않고 실패를 호출자에게 전달합니다.