Computer >> 컴퓨터 >  >> 스마트폰 >> iPhone

Swift를 통한 안전한 iOS 개발:일반적인 함정을 피하고 앱을 강화하세요

Swift를 통한 안전한 iOS 개발:일반적인 함정을 피하고 앱을 강화하세요

요즘에는 공격자가 애플리케이션을 손상시키려고 시도할 수 있는 방법이 많이 있습니다. 그리고 사이버 공격이 지속적으로 증가함에 따라 보안 모바일 애플리케이션, 더 나아가 보안 코딩에 대한 수요가 그 어느 때보다 높아졌습니다.

따라서 iOS 개발자라면 앱 개발의 모든 단계에서 보안을 우선시하는 방법도 배워야 합니다.

Apple의 최신 프로그래밍 언어인 Swift는 보안을 강화하면서 개발을 단순화하는 풍부한 도구와 프레임워크를 제공하지만, 이는 올바르게 사용될 경우에만 가능합니다.

이 기사에서는 Swift 기반 iOS 앱의 일반적인 보안 함정 10가지를 살펴보고 이를 완화하기 위한 실용적인 전략을 제시합니다.

전제조건

시작하기 전에 다음이 필요합니다:

  • Swift 및 iOS 개발에 대한 실무 지식

  • Xcode에 액세스하세요.

  • iOS 앱이 서버와 통신하는 방식에 대한 기본적인 이해.

  • 터미널/명령줄 기본 사항에 익숙합니다.

코드 예제는 실용적이고 단계별로 설명되어 있어 초보 개발자도 쉽게 접근할 수 있으며 앱 보안을 강화하려는 숙련된 개발자에게도 가치를 제공합니다.

우리가 다룰 내용:

  • Swift iOS 애플리케이션에서 가장 널리 퍼진 보안 함정은 무엇입니까?
    • 1. 안전하지 않은 데이터 저장
    • 2. 약한 네트워크 통신
    • 3. 부적절한 입력 검증
    • 4. 하드코딩 비밀
    • 5. 불충분한 인증 및 승인
    • 6. 안전하지 않은 로깅 및 오류 처리
    • 7. 코드 난독화 및 리버스 엔지니어링 무시
    • 8. 안전하지 않은 타사 라이브러리
    • 9. 불충분한 생체 인식 및 다단계 인증
    • 10. 정기적인 보안 테스트 무시

Swift iOS 애플리케이션에서 가장 널리 퍼진 보안 함정은 무엇입니까?

Swift와 iOS는 강력한 보안 기능을 제공하지만 실수는 여전히 발생합니다. 다음은 가장 일반적인 함정과 해결 방법입니다:

1. 안전하지 않은 데이터 저장

개발자가 저지르는 가장 흔한 실수 중 하나는 민감한 데이터를 안전하지 않게 저장하는 것입니다. 비밀번호, 토큰 또는 개별 사용자 데이터도 실수로 UserDefaults 또는 로컬 저장소에 암호화되지 않은 형태로 남겨질 수 있습니다.

UserDefaults는 소량의 데이터에는 편리하지만, 기기가 손상된 경우 공격자가 쉽게 접근할 수 있으므로 민감한 데이터에는 안전하지 않습니다.

수정 방법:

Keychain Services API를 사용하여 민감한 데이터를 안전하게 저장하세요. 키체인은 데이터를 암호화하여 기기에 바인딩하므로 승인되지 않은 다른 애플리케이션이나 사용자가 액세스할 수 없습니다.

KeychainAccess 또는 내장된 SecItemAdd 및 SecItemCopyMatching 기능과 같은 Swift의 라이브러리를 사용하여 자격 증명을 안전하게 저장할 수 있습니다.

예를 들어, 민감한 데이터를 안전하게 저장하기 위해 키체인에 사용자 비밀번호를 저장하는 방법은 다음과 같습니다:

do {
try keychain.set("userPassword123", key: "userPassword")
} catch {
 print("Error saving to Keychain: \(error)")
}

keychain.set("userPassword123", key: "userPassword")에 전화하면 다음과 같은 일이 발생합니다. 저장을 위해 Apple의 기본 보안 프레임워크를 사용하는 KeychainManager 클래스 내부:

import Security
class KeychainManager {
 func set(_ value: String, key: String) throws {
 // 1. Convert string to Data
 guard let data = value.data(using: .utf8) else {
 throw NSError(domain: "KeychainManager", code: -1)
 }
 // 2. Build the query dictionary
 let query: [String: Any] = [
 // Store as password
 kSecClass as String: kSecClassGenericPassword,
 // Your app's bundle identifier
 kSecAttrService as String: "com.yourapp.keychain",
 // "userPassword"
 kSecAttrAccount as String: key,
 // "userPassword123" encrypted
 kSecValueData as String: data,
 kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
 ]
 // 3. Save to keychain (iOS encrypts it automatically)
 let status = SecItemAdd(query as CFDictionary, nil)
 // 4. Check if successful
 guard status == errSecSuccess else {
 throw NSError(domain: "KeychainManager", code: Int(status))
 }
 }
}

이 함수가 실행되면 iOS는 "userPassword123"과 같은 문자열 값을 암호화된 바이너리 데이터로 변환하고 이를 기기의 키체인 데이터베이스에 안전하게 저장합니다. 항목은 제공된 키(예:"userPassword") 아래에 저장되며 귀하의 앱에서만 액세스할 수 있습니다.

그 뒤에서 키체인은 기기별 키를 사용한 하드웨어 기반 암호화, Face ID 또는 Touch ID를 통한 선택적 생체 인식 보호, 앱 수준 격리 등 강력한 보안 기능을 활용하여 다른 앱이 저장된 자격 증명을 읽거나 수정할 수 없도록 합니다.

2. 약한 네트워크 통신

네트워크를 통해 민감한 데이터를 전송하는 것도 취약점이 발생하기 쉬운 또 다른 영역입니다. 암호화되지 않은 HTTP 연결을 사용하면 앱이 중간자(MITM) 공격에 노출되어 공격자가 전송 중인 데이터를 가로채고 수정할 수 있습니다.

문제 :안전하지 않은 연결을 통해 앱과 서버 간에 데이터가 이동하는 경우 동일한 네트워크(예:공용 Wi-Fi)에 있는 공격자는 다음을 수행할 수 있습니다.

  • 민감한 정보(비밀번호, 개인정보, 결제 세부정보) 읽기

  • 요청 및 응답 수정

  • 합법적인 서버로 가장

수정 방법:

1. 항상 HTTPS 사용
HTTPS는 전송 중인 모든 데이터를 암호화하여 공격자가 읽을 수 없도록 만듭니다. iOS의 ATS(앱 전송 보안)는 기본적으로 안전하지 않은 HTTP 연결을 차단하여 이를 시행합니다.

// ❌ INSECURE - HTTP connection (blocked by ATS by default)
let url = URL(string: "http://api.example.com/login")
// ✅ SECURE - HTTPS connection
let url = URL(string: "https://api.example.com/login")

꼭 필요한 경우가 아니면 Info.plist에 ATS 예외를 추가하지 않는 것이 좋습니다. 타사 API가 HTTP만 지원하는 경우 해당 API에 문의하여 업그레이드하거나 보다 안전한 대안을 찾으세요.

2. 인증서 고정 구현(고급 보호)

HTTPS를 사용하더라도 앱은 여전히 정교한 MITM 공격에 취약할 수 있습니다. 예를 들어 공격자는 맬웨어나 사회 공학을 통해 사용자의 장치에 사기성 인증서를 설치하고 유효한 것처럼 보이는 HTTPS 트래픽을 가로챌 수 있습니다. 공격자의 가짜 인증서는 장치에서 신뢰되므로 "보안" 통신을 해독하고 읽을 수 있습니다.

인증서 고정은 앱이 특정 서버의 인증서만 신뢰하고 다른 인증서는 모두 거부함으로써 이 문제를 해결합니다.

인증서 고정 작동 방식:

앱은 예상되는 인증서(또는 해당 공개 키 해시)를 저장하고 각 연결 중에 이를 검증합니다.

class SecureNetworkManager: NSObject, URLSessionDelegate {
 // Store your server's certificate hash
 // Get this by running: openssl x509 -in certificate.crt -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | base64
 private let expectedPublicKeyHash = "YOUR_CERTIFICATE_HASH_HERE"
 func urlSession(
 _ session: URLSession,
 didReceive challenge: URLAuthenticationChallenge,
 completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
 ) {
 // Step 1: Check if this is a server trust challenge (certificate validation)
 guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
 let serverTrust = challenge.protectionSpace.serverTrust
 else {
 // Not a certificate challenge; use default handling
 completionHandler(.performDefaultHandling, nil)
 return
 }
 // Step 2: Validate that the server's certificate matches our pinned certificate
 if isValidServerTrust(serverTrust) {
 // Certificate matches - proceed with the connection
 completionHandler(.useCredential, URLCredential(trust: serverTrust))
 } else {
 // Certificate doesn't match - reject the connection to prevent MITM attack
 completionHandler(.cancelAuthenticationChallenge, nil)
 }
 }
 // Validates the server's certificate against our pinned hash
 private func isValidServerTrust(_ serverTrust: SecTrust) -> Bool {
 // Extract the server's certificate
 guard let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
 return false
 }
 // Get the public key from the certificate
 let serverPublicKey = SecCertificateCopyKey(serverCertificate)
 guard let publicKey = serverPublicKey else {
 return false
 }
 // Convert the public key to data and hash it
 var error: Unmanaged<CFError>?
 guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else {
 return false
 }
 // Hash the public key using SHA-256
 let publicKeyHash = SHA256.hash(data: publicKeyData)
 let publicKeyHashString = Data(publicKeyHash).base64EncodedString()
 // Compare with our expected hash
 return publicKeyHashString == expectedPublicKeyHash
 }
}

이 코드의 단계별 기능은 다음과 같습니다.

  1. urlSession(_:didReceive:completionHandler:) – 이 메서드는 앱이 HTTPS 연결을 설정할 때마다 호출됩니다. iOS에서 "이 서버의 인증서를 신뢰해야 합니까?"라고 묻습니다.

  2. 인증 방법 확인 – 이것이 다른 유형의 인증이 아닌 서버 신뢰 문제(인증서 확인)인지 확인합니다.

  3. 인증서 유효성을 검사합니다. isValidServerTrust()으로 전화합니다. , 이는 다음과 같습니다:

    • 연결에서 서버의 인증서를 추출합니다

    • 해당 인증서에서 공개 키를 가져옵니다

    • SHA-256을 사용하여 공개 키를 해시합니다

    • 해시를 저장된 예상 해시와 비교합니다

  1. 결정하기:
  • 해시가 일치하면 서버가 합법적인 것입니다. .useCredential로 진행하세요 .

  • 해시가 일치하지 않으면 잠재적인 MITM 공격이 발생합니다. .cancelAuthenticationChallenge로 취소 .

그렇다면 이것이 어떻게 MITM 공격을 방지할 수 있을까요? 공격자가 사용자의 장치에 사기성 인증서를 설치하고 트래픽을 가로채더라도 해당 인증서의 해시는 고정된 해시와 일치하지 않습니다. 앱이 연결을 거부하여 공격자가 트래픽을 해독하는 것을 방지합니다.

3. 추가 보호:공용 Wi-Fi에서 VPN 권장

또한 보안 강화를 위해 사용자가 공용 Wi-Fi를 사용할 때 VPN을 통해 연결하도록 권장하고 데이터를 안전하게 유지하기 위해 VPN을 효과적으로 사용하는 방법에 대한 지침을 제공할 수도 있습니다.

개발자의 경우 강력한 앱 보안을 유지하는 것은 잘 최적화된 시스템을 갖추는 데 달려 있습니다. 느린 Mac의 속도를 높이는 방법을 배우면 더 원활한 빌드, 더 빠른 테스트 및 더 안전한 전체 개발 작업 흐름을 보장하는 데 도움이 될 수 있습니다.

3. 부적절한 입력 검증

일부 개발자는 올바른 입력 유효성 검사를 무시하여 SQL 삽입, 원격 코드 실행 및 데이터 손상을 비롯한 여러 가지 취약점을 발생시킵니다.

Swift는 강력한 타이핑 지원을 제공하지만 일부 개발자는 사용자 입력이나 API 응답을 삭제하지 않습니다. 실시간 API 이메일 확인 기능을 통합하면 사용자가 이메일 주소를 저장하거나 처리하기 전에 합법적이고 올바른 형식의 이메일 주소를 제공할 수 있어 보안 위험과 데이터 품질 문제가 모두 줄어듭니다.

수정 방법:

입력 검증은 악성 데이터에 대한 첫 번째 방어선입니다. iOS 애플리케이션을 보호하는 방법은 다음과 같습니다.

1. 패턴으로 사용자 입력 검증

처리하기 전에 항상 정규식이나 사전 정의된 패턴을 사용하여 사용자 입력의 유효성을 검사하십시오. 예를 들어, 이메일 주소를 수락하는 경우:

func isValidEmail(_ email: String) -> Bool {
 let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
 let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
 return emailPredicate.evaluate(with: email)
}
// Only accept properly formatted data
guard isValidEmail(userEmail) else {
 // Reject invalid input
 return
}

이렇게 하면 올바른 형식의 데이터만 허용되어 형식이 잘못되었거나 악의적인 입력이 시스템에 입력되는 것을 방지할 수 있습니다.

2. API 응답 삭제

외부 데이터를 절대 신뢰하지 마십시오. API 응답을 사용하기 전에 항상 검증하고 정리하세요:

if let userAge = apiResponse["age"] as? Int,
 userAge >= 0 && userAge <= 150 {
 // Safe to use
 user.age = userAge
} else {
 // Handle invalid data appropriately
 throw ValidationError.invalidAge
}

3. 매개변수화된 쿼리 사용(가장 중요)

가장 위험한 실수는 문자열 연결을 통해 데이터베이스 쿼리를 작성하는 것입니다. 다음 취약 코드를 고려해보세요:

// ❌ NEVER DO THIS - Vulnerable to SQL Injection
let username = userInput // Could be: "admin' OR '1'='1"
let query = "SELECT * FROM users WHERE username = '\(username)'"
database.execute(query)

악의적인 사용자가 사용자 이름으로 admin' 또는 '1'='1을 입력하면 쿼리는 다음과 같습니다:

SELECT * FROM users WHERE username = 'admin' OR '1'='1'

이렇게 하면 단 한 명이 아닌 데이터베이스의 모든 사용자가 반환되어 잠재적으로 민감한 데이터가 노출될 수 있습니다. 보안 솔루션은 매개변수화된 쿼리를 사용합니다:

// ✅ SAFE - Using parameterized queries
let query = "SELECT * FROM users WHERE username = ?"
database.execute(query, withArgumentsIn: [username])

이 접근 방식에서는 ? 데이터베이스가 SQL 명령의 일부가 아닌 매개변수로 처리하는 자리 표시자입니다. 사용자 이름 값은 withArgumentsIn에 별도로 전달됩니다. 배열.

즉, 사용자가 admin' OR '1'='1와 같은 SQL 코드를 삽입하려고 해도 , 데이터베이스는 전체 문자열을 실행 가능한 SQL 코드가 아닌 검색할 리터럴 사용자 이름으로 처리합니다. 데이터베이스 엔진은 특수 문자를 자동으로 이스케이프 처리하여 SQL 삽입 위험을 완전히 제거합니다.

매개변수화된 쿼리는 쿼리 구조를 데이터에서 분리함으로써 사용자 입력이 SQL 문의 의도된 논리를 결코 변경할 수 없도록 보장합니다.

4. 하드코딩 비밀

소스 코드에 하드 코딩된 API 키, 자격 증명 또는 개인 토큰은 또 다른 심각한 보안 실수입니다. 공격자는 특히 대중에게 출시된 앱의 경우 리버스 엔지니어링 도구를 사용하여 컴파일된 바이너리에서 이러한 비밀을 추출할 수 있습니다.

일단 노출되면 이러한 자격 증명을 사용하여 백엔드 서비스에 액세스할 수 있으며 잠재적으로 데이터 침해 또는 무단 청구로 이어질 수 있습니다.

문제 – 하드코딩된 비밀:

// NEVER DO THIS
class APIClient {
 private let apiKey = "1234567890abcdef"
 private let secretToken = "sk_live_51H..."
 func makeRequest() {
 // These secrets are embedded in your binary
 let headers = ["Authorization": "Bearer \(apiKey)"]
 }
}

수정 방법:

민감한 자격 증명을 코드에 직접 저장하지 마세요. 안전한 대안은 다음과 같습니다:

해결책 1:런타임 시 백엔드에서 비밀 가져오기

가장 안전한 접근 방식은 클라이언트에 비밀을 전혀 저장하지 않는 것입니다. 대신 사용자를 인증하고 백엔드에서 승인된 API 호출을 수행하도록 하세요.

class APIClient {
 private var sessionToken: String?
 // User logs in and receives a temporary session token
 func authenticateUser(email: String, password: String) async throws {
 let response = try await backend.login(email: email, password: password)
 // Store only a temporary, user-specific session token
 self.sessionToken = response.sessionToken
 }
 // Backend handles the actual API calls with the real API key
 func fetchUserData() async throws -> UserData {
 guard let token = sessionToken else {
 throw AuthError.notAuthenticated
 }
 // Your backend receives this request, validates the session token,
 // then uses its own API keys to fetch data from third-party services
 return try await backend.getUserData(sessionToken: token)
 }
}

작동 방식:

귀하의 앱은 실제 API 키를 결코 알지 못합니다. 사용자에게 데이터가 필요할 때 앱은 세션 토큰과 함께 자체 백엔드 서버에 요청을 보냅니다. 백엔드는 토큰의 유효성을 검사한 다음 안전하게 저장된 자체 API 키를 사용하여 실제 타사 API 호출을 수행합니다. 이렇게 하면 실제 비밀이 서버를 떠나지 않습니다.

해결책 2:환경 변수 또는 구성 파일(개발 전용)

개발 환경의 경우 버전 제어에서 제외되는 .xcconfig 파일을 사용하세요.

// Secrets.xcconfig (add to .gitignore!)
API_KEY = your_dev_api_key_here
API_SECRET = your_dev_secret_here
// Access in your code through Info.plist
class Config {
 static let apiKey: String = {
 guard let key = Bundle.main.object(forInfoDictionaryKey: "API_KEY") as? String else {
 fatalError("API_KEY not found in configuration")
 }
 return key
 }()
}

중요 :이 접근 방식은 비프로덕션 환경에만 적합합니다! 구성 파일에서도 프로덕션 API 키를 앱과 함께 제공하지 마세요.

5. 불충분한 인증 및 승인

클라이언트 측 인증 및 권한 부여 확인에 의존하는 것은 위험합니다. 공격자는 무차별 대입 공격이나 앱/런타임 변조를 통해 앱이 이러한 검사를 우회하고 무단으로 액세스하도록 할 수 있습니다.

수정 방법:

  • 클라이언트 측이 아닌 서버 측에서 인증 및 승인을 수행하세요.

  • 인증된 사용자 로그인을 위해 JWT(JSON 웹 토큰) 또는 OAuth 2.0을 사용하세요.

  • 토큰 도용 가능성을 최소화하려면 토큰 만료 및 새로 고침 논리를 구현해야 합니다.

예:JWT를 안전하게 전송:

let request = URLRequest(url: apiURL)
request.setValue("Bearer \(jwtToken)", forHTTPHeaderField: "Authorization")

6. 안전하지 않은 로깅 및 오류 처리

광범위하고 안전하지 않은 로깅 관행과 포착되지 않는 예외로 인해 사용자 이름, 비밀번호, API 키를 비롯한 민감한 정보가 노출될 수 있습니다.

수정 방법:

  • 민감한 정보는 주의 깊게 기록하세요.

  • 제어된 오류 관리를 구현하고 사용자에게 표시되는 메시지에 최소한의 정보를 제공합니다.

  • 개인 데이터를 마스킹하거나 암호화하는 보안 로깅 라이브러리를 구현하세요.

do {
 try someRiskyOperation()
} catch {
 // Log error securely
 Logger.log("Operation failed: \(error.localizedDescription)")
}

7. 코드 난독화 및 리버스 엔지니어링 무시

Swift 바이너리는 리버스 엔지니어링되어 민감한 비즈니스 로직, 알고리즘 또는 숨겨진 비밀을 노출할 수 있습니다. 공격자는 Hopper Disassembler, 클래스 덤프 또는 IDA Pro와 같은 도구를 사용하여 앱을 디컴파일하고 내부적으로 작동하는 방식을 분석합니다. 이러한 위험은 특히 소규모 앱의 경우 과소평가되는 경우가 많지만 모든 앱이 표적이 될 수 있습니다.

이는 Swift 앱을 컴파일할 때 결과 바이너리에 다음이 포함된다는 의미입니다.

  • 클래스 이름 및 메소드 서명

  • 문자열 리터럴(URL, 오류 메시지, 키)

  • 코드 로직의 구조

  • 알고리즘 구현

공격자는 이 정보를 추출하여 이를 사용하여 앱의 인증 흐름을 이해하고 이를 우회하고, 독점 알고리즘을 복사하고, '숨겨져 있다'고 생각했던 하드코딩된 API 엔드포인트 또는 키를 찾고, 비용을 지불하지 않고 잠금 해제할 수 있는 프리미엄 기능을 찾는 등의 작업을 할 수 있습니다.

나쁜 이유 - 실제 예:

앱에 프리미엄 기능 확인이 있다고 가정해 보겠습니다.

class FeatureManager {
 func isPremiumUser() -> Bool {
 // Check if user has premium access
 let hasSubscription = UserDefaults.standard.bool(forKey: "premium_unlocked")
 return hasSubscription
 }
 func unlockPremiumFeature() {
 guard isPremiumUser() else {
 showPaywall()
 return
 }
 // Show premium content
 showPremiumContent()
 }
}

공격자는 앱을 리버스 엔지니어링하여 isPremiumUser() 메서드를 발견할 수 있습니다. 액세스를 제어하며 단순히 UserDefaults만 확인합니다. premium_unlocked라는 키 . 그런 다음 런타임 조작 도구를 사용하여 페이월을 완전히 우회하여 이 값을 true로 설정할 수 있다는 것을 알게 됩니다.

수정 방법:

1. Swift 컴파일러 최적화 사용

디버깅 기호를 제거하고 바이너리를 읽기 어렵게 만드는 최적화 플래그를 활성화합니다:

// In your build settings:
// - Set "Optimization Level" to "-O" (or -Osize) for release builds
// - Enable "Strip Debug Symbols During Copy" = YES
// - Set "Strip Style" to "All Symbols"

이렇게 하면 함수 이름이 제거되고 컴파일된 코드의 가독성이 떨어지게 됩니다. 단, 클래스/메서드 이름은 부분적으로 표시됩니다.

2. 기호 난독화 도구 사용

SwiftShield와 같은 도구는 클래스, 메소드 및 속성의 이름을 의미 없는 이름으로 바꿀 수 있습니다:

// Before obfuscation (readable to attackers):
class FeatureManager {
 func isPremiumUser() -> Bool { ... }
}
// After obfuscation (harder to understand):
class a7f3b2 {
 func x9k2m() -> Bool { ... }
}

이렇게 하면 리버스 엔지니어링이 방지되지는 않지만 공격자가 코드의 기능을 이해하기가 훨씬 더 어려워집니다.

3. 민감한 로직을 서버로 이동(모범 사례)

로컬에서 프리미엄 상태를 확인하는 대신 서버 측에서 확인하세요.

// ✅ Secure approach - Server validates everything
class FeatureManager {
 func unlockPremiumFeature() async {
 do {
 // Server checks if user truly has premium access
 let hasAccess = try await backend.verifyPremiumAccess(userId: currentUserId)
 if hasAccess {
 showPremiumContent()
 } else {
 showPaywall()
 }
 } catch {
 // Handle error
 showPaywall()
 }
 }
}

작동 방식:

백엔드는 프리미엄 액세스에 대한 정보 소스를 유지합니다. 공격자가 앱을 리버스 엔지니어링하고 검사를 우회하려고 시도하더라도 서버는 승인되지 않은 요청을 거부합니다. 앱은 단순한 UI 레이어가 되며 모든 중요한 결정은 공격자가 조작할 수 없는 서버 측에서 이루어집니다.

핵심 원칙은 앱 코드가 공개라고 가정하는 것입니다. 즉, 결제, 액세스 제어 또는 인증과 같은 보안이 중요한 작업에 대해 클라이언트 측 검사에 의존하지 마십시오. 난독화를 사용하면 리버스 엔지니어링이 더 어려워지지만 궁극적으로는 민감한 로직을 보안 백엔드로 옮깁니다.

8. 안전하지 않은 타사 라이브러리

타사 라이브러리가 해킹되거나 오래된 경우 위험에 처해 있습니다. 개발자는 종속성으로 인한 잠재적 보안 위험보다 앱 기능의 우선순위를 실수로 지정할 수 있으며, ETL 도구는 종속성 관련 데이터의 모니터링 및 처리를 간소화하여 취약성을 보다 효율적으로 식별함으로써 더욱 도움을 줄 수 있습니다.

더 넓은 규모에서 강력한 데이터 센터 보안 관행을 구현하면 타사 구성 요소로 인해 위험이 발생하더라도 기본 인프라가 공격에 대해 탄력성을 유지할 수 있습니다.

수정 방법:

  • 고품질의 잘 관리된 라이브러리만 사용하세요.

  • 종속성을 업데이트하고 CVE(공통 취약점 및 노출)를 모니터링합니다.

  • 민감한 데이터를 처리하는 경우 라이브러리 코드를 감사하세요.

9. 불충분한 생체인식 및 다단계 인증

대부분의 애플리케이션은 비밀번호에만 의존하므로 해킹에 취약합니다. Face ID나 Touch ID와 같은 생체인식을 활성화하면 사용자 보안이 강화됩니다.

수정 방법:

  • 생체 인증을 위한 LocalAuthentication 프레임워크를 연결합니다.

  • 다중 인증(MFA)을 위해 생체인식과 서버 기반 인증을 결합합니다.

import LocalAuthentication
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
 context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
 localizedReason: "Access your account") { success, authError in
 DispatchQueue.main.async {
 if success {
 // Proceed securely
 } else {
 // Handle failure (authError may contain the reason)
 print("Authentication failed: \(authError?.localizedDescription ?? "Unknown error")")
 }
 }
}
} else {
// Biometrics not available, check error for details
 print("Biometrics unavailable: \(error?.localizedDescription ?? "Unknown error")")
}

10. 정기 보안 테스트 무시

앱은 개발 중 모범 사례를 따랐음에도 불구하고 복잡한 상호 작용, 타사 종속성 또는 새로 발견된 공격 벡터에서 나타나는 미확인 취약점을 포함하는 경우가 많습니다. 공격자가 취약점을 악용하기 전에 이러한 취약점을 발견하려면 정기적인 보안 테스트가 절대적으로 필요합니다.

보안 테스트는 접근 가능한 도구와 방법을 사용하여 여러 단계에서 이루어져야 합니다.

  1. 자동 보안 검색: 일반적인 문제를 파악하기 위해 모든 빌드에서 자동으로 실행됩니다.

  2. 자체 코드 감사: 확립된 지침을 사용하여 자신의 코드를 정기적으로 보안 중심으로 검토합니다.

  3. 취약점 검사 도구: MobSF와 같은 무료 도구를 사용하여 앱의 보안 결함을 분석하세요.

  4. 종속성 감사: 알려진 보안 취약점이 있는지 타사 라이브러리를 확인하고 있습니다.

수정 방법:

1. CI/CD에 자동 보안 검색 구현

모든 코드 변경 사항이 자동으로 확인되도록 보안 검색 도구를 지속적인 통합 파이프라인에 통합하세요.

# Example: GitHub Actions workflow for automated security scanning
name: Security Scan
on: [push, pull_request]
jobs:
 security-scan:
 runs-on: macos-latest
 steps:
 - name: Checkout code
 uses: actions/checkout@v3
 - name: Run MobSF Security Scan
 run: |
 # Mobile Security Framework - scans for common vulnerabilities
 docker run -v $(pwd):/app opensecurity/mobile-security-framework-mobsf
 - name: Dependency Vulnerability Check
 run: |
 # Check CocoaPods/SPM dependencies for known CVEs
 brew install dependency-check
 dependency-check --scan ./Podfile.lock --format JSON
 - name: Secret Detection
 run: |
 # Detect accidentally committed secrets
 brew install truffleHog
 truffleHog filesystem . --json
 - name: Fail build on critical issues
 run: |
 if grep -q "CRITICAL" security-report.json; then
 echo "Critical security issues found!"
 exit 1
 fi

자동 스캔 확인 사항:

  • 하드코딩된 API 키, 토큰 또는 비밀번호

  • 안전하지 않은 네트워크 구성(HTTPS 대신 HTTP 허용)

  • 약한 암호화 알고리즘

  • 타사 라이브러리의 알려진 취약점

  • 부적절한 SSL/TLS 인증서 검증

  • 안전하지 않은 데이터 저장(UserDefaults에 민감한 데이터 저장)

  • 과도한 앱 권한

자동 스캔의 출력 예:

Security Scan Results:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[CRITICAL] Hardcoded API Key Found
 File: APIClient.swift:15
 Issue: API key "sk_live_abc123..." detected in source code
[HIGH] Insecure HTTP Connection
 File: NetworkManager.swift:42
 Issue: App allows cleartext HTTP traffic to api.example.com
 Fix: Enforce HTTPS or add exception to Info.plist if required
[MEDIUM] Weak Encryption Algorithm
 File: DataEncryption.swift:28
 Issue: Using MD5 for hashing (cryptographically broken)
 Fix: Use SHA-256 or better
[LOW] Outdated Dependency
 Library: Alamofire 4.2.0
 Issue: Known vulnerability CVE-2021-12345
 Fix: Update to version 5.6.0 or later
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Build Failed: 2 critical issues must be fixed before deployment

2. 취약점 분석을 위해 MobSF(모바일 보안 프레임워크) 사용

MobSF는 iOS 앱의 보안 문제를 분석하는 무료 자동화 도구입니다.

# Install and run MobSF locally
docker pull opensecurity/mobile-security-framework-mobsf
docker run -it -p 8000:8000 opensecurity/mobile-security-framework-mobsf
# Upload your .ipa file through the web interface at localhost:8000
# MobSF will analyze and provide a detailed security report

MobSF가 확인하는 사항:

  • 하드코딩된 비밀에 대한 바이너리 분석

  • 안전하지 않은 데이터 저장 패턴

  • 약한 암호화 구현

  • 안전하지 않은 네트워크 연결

  • 코드 품질 및 보안 모범 사례

  • 보안 표준 준수

3. OWASP MSTG를 사용하여 정기적인 코드 감사 수행

OWASP 모바일 보안 테스트 가이드를 체크리스트로 사용하여 자신의 코드를 감사하세요.

// Example: Following OWASP MSTG recommendations for secure storage
class SecureStorage {
 // ❌ Insecure - UserDefaults is not encrypted
 func saveTokenInsecurely(_ token: String) {
 UserDefaults.standard.set(token, forKey: "authToken")
 }
 // ✅ Secure - Using Keychain as OWASP recommends
 func saveTokenSecurely(_ token: String) throws {
 let data = token.data(using: .utf8)!
 let query: [String: Any] = [
 kSecClass as String: kSecClassGenericPassword,
 kSecAttrAccount as String: "authToken",
 kSecValueData as String: data,
 kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
 ]
 SecItemDelete(query as CFDictionary)
 let status = SecItemAdd(query as CFDictionary, nil)
 guard status == errSecSuccess else {
 throw StorageError.saveFailed
 }
 }
}

자체 감사를 위한 OWASP MSTG 체크리스트:

  • [ ] 저장된 모든 민감한 데이터는 암호화됩니까?

  • [ ] 모든 네트워크 호출에 HTTPS가 적용됩니까?

  • [ ] 인증서가 제대로 검증되었나요?

  • [ ] 민감한 데이터는 로그에서 제외되나요?

  • [ ] API 키와 비밀이 하드코딩되지 않았나요?

  • [ ] 사용자 입력이 검증되고 삭제됩니까?

  • [ ] 인증 토큰은 키체인에 안전하게 저장되어 있나요?

4. 자동 종속성 검색

새로 발견된 취약점이 있는지 종속성을 지속적으로 모니터링하세요.

# For CocoaPods projects
gem install cocoapods-audit
pod audit
# For Swift Package Manager
# Use GitHub Dependabot (free for public repos) or
brew install swift-outdated
swift-outdated

다음 도구를 사용하여 자동 알림을 설정하세요.

  • GitHub 종속봇: 취약한 종속성이 감지되면 자동으로 PR 생성(무료)

  • 스닉 :오픈 소스 프로젝트에 무료 등급 사용 가능

  • OWASP 종속성 확인: 무료 명령줄 도구

결론

Swift를 사용하여 보안 iOS 앱을 개발하는 것은 모두 미래 지향적인 사고에 관한 것입니다. 안전하지 않은 데이터 저장, 열악한 네트워크 통신, 하드 코딩된 비밀 또는 열악한 인증과 같은 일반적인 오류를 방지하기 위해 최선을 다해야 합니다.

기밀 정보에 키체인을 사용하고 HTTPS, 입력 확인 및 다단계 인증을 요구하는 것은 모두 위험을 줄이는 단계입니다.

보안 취약성을 정기적으로 테스트하고 타사 라이브러리 사용을 제한하는 것도 앱의 보안을 더욱 강화할 수 있습니다.

보안은 지속적인 책임입니다. Swift는 도구를 제공하지만 개발자는 이러한 도구를 신중하게 적용해야 합니다. 시작부터 바로 보안을 다루면 사용자 정보를 보호하고 신뢰를 쌓으며 애플리케이션의 평판을 보호할 수 있습니다.

무료로 코딩을 배우세요. freeCodeCamp의 오픈 소스 커리큘럼은 40,000명 이상의 사람들이 개발자로 취업하는 데 도움을 주었습니다. 시작하세요