iOS 애플리케이션 보안 강화 기법

HTTPS 통신 구현

iOS 앱에서 네트워크 통신 시 보안을 강화하기 위해 HTTPS 프로토콜 사용은 필수적입니다. Info.plist 파일에 다음 설정을 추가하여 ATS(App Transport Security)를 활성화합니다.

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
</dict>

SSL 인증서 고정 구현

서버 인증서를 직접 검증하여 위조된 서버로의 연결을 차단하는 SSL Pinning 기술을 적용할 수 있습니다.

import Foundation

class NetworkSecurityDelegate: NSObject, URLSessionDelegate {
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        guard let serverTrust = challenge.protectionSpace.serverTrust,
              let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        
        let serverCertData = SecCertificateCopyData(certificate) as Data
        guard let bundledCertPath = Bundle.main.path(forResource: "server_certificate", ofType: "der"),
              let bundledCertData = try? Data(contentsOf: URL(fileURLWithPath: bundledCertPath)),
              serverCertData == bundledCertData else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        
        let credential = URLCredential(trust: serverTrust)
        completionHandler(.useCredential, credential)
    }
}

// 활용 예시
let requestURL = URL(string: "https://api.secure-service.com")!
let secureSession = URLSession(configuration: .default, delegate: NetworkSecurityDelegate(), delegateQueue: nil)
let dataTask = secureSession.dataTask(with: requestURL) { data, response, error in
    // 응답 처리 로직
}
dataTask.resume()

SQL Injection 방지 처리

사용자 입력값을 처리할 때는 반드시 파라미터화된 쿼리를 사용하여 SQL Injection 공격을 방지해야 합니다.

import SQLite3

func executeSecureQuery(inputValue: String) {
    var database: OpaquePointer?
    guard sqlite3_open(databasePath, &database) == SQLITE_OK else { return }
    
    var statement: OpaquePointer?
    let queryString = "SELECT * FROM members WHERE email = ?"
    
    if sqlite3_prepare_v2(database, queryString, -1, &statement, nil) == SQLITE_OK {
        sqlite3_bind_text(statement, 1, inputValue, -1, nil)
        
        while sqlite3_step(statement) == SQLITE_ROW {
            // 결과 처리
        }
    }
    
    sqlite3_finalize(statement)
    sqlite3_close(database)
}

민감 데이터 암호화 저장

키체인(Keychain) 서비스를 활용하여 사용자의 민감 정보를 안전하게 저장할 수 있습니다.

import Security

func storeEncryptedData(identifier: String, content: Data) -> Bool {
    let attributes: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: identifier,
        kSecValueData as String: content
    ]
    
    SecItemDelete(attributes as CFDictionary)
    return SecItemAdd(attributes as CFDictionary, nil) == errSecSuccess
}

func retrieveEncryptedData(identifier: String) -> Data? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: identifier,
        kSecReturnData as String: kCFBooleanTrue!,
        kSecMatchLimit as String: kSecMatchLimitOne
    ]
    
    var result: AnyObject?
    let status = SecItemCopyMatching(query as CFDictionary, &result)
    
    return status == errSecSuccess ? result as? Data : nil
}

입력값 유효성 검사 및 정제

사용자로부터 받은 입력값은 반드시 검증 및 정제 과정을 거쳐 XSS(Cross-Site Scripting)와 같은 공격을 방지해야 합니다.

func validateAndClean(input: String) -> String {
    var cleaned = input
    let dangerousPatterns = ["<script>", "</script>", "javascript:", "onload=", "onclick="]
    
    for pattern in dangerousPatterns {
        cleaned = cleaned.replacingOccurrences(of: pattern, with: "", options: [.caseInsensitive])
    }
    
    return cleaned
}

// 사용 예제
let rawInput = "<script>alert('보안 위협')</script>"
let safeInput = validateAndClean(input: rawInput)
print(safeInput) // 출력: alert('보안 위협')

Objective-C 버전 구현

HTTPS 설정

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <false/>
</dict>

SSL 인증서 검증

#import <Foundation/Foundation.h>

@interface SecureConnectionDelegate : NSObject <NSURLSessionDelegate>
@end

@implementation SecureConnectionDelegate

- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))handler {
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        SecCertificateRef cert = SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, 0);
        NSData *serverCertData = CFBridgingRelease(SecCertificateCopyData(cert));
        
        NSString *certPath = [[NSBundle mainBundle] pathForResource:@"certificate" ofType:@"der"];
        NSData *bundledCertData = [NSData dataWithContentsOfFile:certPath];
        
        if ([serverCertData isEqualToData:bundledCertData]) {
            NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            handler(NSURLSessionAuthChallengeUseCredential, credential);
            return;
        }
    }
    handler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}

@end

파라미터화된 SQL 쿼리

#import <sqlite3.h>

- (void)performSecureDatabaseQuery:(NSString *)userEmail {
    sqlite3 *dbHandle;
    if (sqlite3_open([databasePath UTF8String], &dbHandle) == SQLITE_OK) {
        sqlite3_stmt *preparedStmt;
        const char *sqlQuery = "SELECT * FROM members WHERE email = ?";
        
        if (sqlite3_prepare_v2(dbHandle, sqlQuery, -1, &preparedStmt, NULL) == SQLITE_OK) {
            sqlite3_bind_text(preparedStmt, 1, [userEmail UTF8String], -1, SQLITE_TRANSIENT);
            
            while (sqlite3_step(preparedStmt) == SQLITE_ROW) {
                // 데이터 처리
            }
        }
        
        sqlite3_finalize(preparedStmt);
        sqlite3_close(dbHandle);
    }
}

키체인 데이터 관리

#import <Security/Security.h>

- (BOOL)saveSensitiveData:(NSData *)data forKey:(NSString *)key {
    NSDictionary *attributes = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
                                (__bridge id)kSecAttrAccount: key,
                                (__bridge id)kSecValueData: data};
    
    SecItemDelete((__bridge CFDictionaryRef)attributes);
    return SecItemAdd((__bridge CFDictionaryRef)attributes, NULL) == errSecSuccess;
}

- (NSData *)loadSensitiveDataForKey:(NSString *)key {
    NSDictionary *searchQuery = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
                                 (__bridge id)kSecAttrAccount: key,
                                 (__bridge id)kSecReturnData: (__bridge id)kCFBooleanTrue,
                                 (__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne};
    
    CFTypeRef resultRef = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)searchQuery, &resultRef);
    
    return status == noErr ? CFBridgingRelease(resultRef) : nil;
}

입력값 보안 처리

- (NSString *)sanitizeUserContent:(NSString *)content {
    NSArray *patterns = @[@"<script>", @"</script>", @"javascript:", @"onload=", @"onclick="];
    NSMutableString *cleanedContent = [content mutableCopy];
    
    for (NSString *pattern in patterns) {
        NSRange range = [cleanedContent rangeOfString:pattern options:NSCaseInsensitiveSearch];
        while (range.location != NSNotFound) {
            [cleanedContent deleteCharactersInRange:range];
            range = [cleanedContent rangeOfString:pattern options:NSCaseInsensitiveSearch];
        }
    }
    
    return [cleanedContent copy];
}

태그: iOS Swift Objective-C Network Security SSL Pinning

6월 11일 17:00에 게시됨