Upgrade to Pro — share decks privately, control downloads, hide ads and more …

iOS Security Deep Dive

Charles Lima
November 10, 2019

iOS Security Deep Dive

This deck covers some important security topics for an iOS app. These topics include http vs https, ssl/tls pinning and insecure data storage, for example. Moreover, it shows how an attacker may explore a few vulnerabilities in your apps and how to defend against them.

Charles Lima

November 10, 2019
Tweet

Other Decks in Programming

Transcript

  1. SSL/TLS Pinning Making sure that the certificate you received is

    the exact same one you have in your bundle.
  2. Certificate ➜ ~ openssl s_client -connect api.themoviedb.org:443 </dev/null \ |

    /dev/null|openssl x509 -outform DER >themoviedb.cer
  3. Certificate ➜ ~ openssl s_client -connect api.themoviedb.org:443 </dev/null \ |

    /dev/null|openssl x509 -outform DER >themoviedb.cer
  4. import Moya import Alamofire class AlamofireSessionManagerBuilder { var policies: [String:

    ServerTrustPolicy]? var configuration = URLSessionConfiguration.default init() { let allPublicKeys = ServerTrustPolicy.pinPublicKeys( publicKeys: ServerTrustPolicy.publicKeys(), validateCertificateChain: true, validateHost: true ) self.policies = [ "api.themoviedb.org": allPublicKeys ] } func build() -> Manager { var serverTrustPolicyManager: ServerTrustPolicyManager? if let policies = self.policies { serverTrustPolicyManager = ServerTrustPolicyManager(policies: policies) } let manager = Manager(configuration: configuration, serverTrustPolicyManager: serverTrustPolicyManager) manager.startRequestsImmediately = false return manager } } Alamofire Implementation
  5. import Moya import Alamofire class AlamofireSessionManagerBuilder { var policies: [String:

    ServerTrustPolicy]? var configuration = URLSessionConfiguration.default init() { let allPublicKeys = ServerTrustPolicy.pinPublicKeys( publicKeys: ServerTrustPolicy.publicKeys(), validateCertificateChain: true, validateHost: true ) self.policies = [ "api.themoviedb.org": allPublicKeys ] } func build() -> Manager { var serverTrustPolicyManager: ServerTrustPolicyManager? if let policies = self.policies { serverTrustPolicyManager = ServerTrustPolicyManager(policies: policies) } let manager = Manager(configuration: configuration, serverTrustPolicyManager: serverTrustPolicyManager) manager.startRequestsImmediately = false return manager } } Alamofire Implementation
  6. import Moya import Alamofire class AlamofireSessionManagerBuilder { var policies: [String:

    ServerTrustPolicy]? var configuration = URLSessionConfiguration.default init() { let allPublicKeys = ServerTrustPolicy.pinPublicKeys( publicKeys: ServerTrustPolicy.publicKeys(), validateCertificateChain: true, validateHost: true ) self.policies = [ "api.themoviedb.org": allPublicKeys ] } func build() -> Manager { var serverTrustPolicyManager: ServerTrustPolicyManager? if let policies = self.policies { serverTrustPolicyManager = ServerTrustPolicyManager(policies: policies) } let manager = Manager(configuration: configuration, serverTrustPolicyManager: serverTrustPolicyManager) manager.startRequestsImmediately = false return manager } } Alamofire Implementation
  7. import Moya import Alamofire class AlamofireSessionManagerBuilder { var policies: [String:

    ServerTrustPolicy]? var configuration = URLSessionConfiguration.default init() { let allPublicKeys = ServerTrustPolicy.pinPublicKeys( publicKeys: ServerTrustPolicy.publicKeys(), validateCertificateChain: true, validateHost: true ) self.policies = [ "api.themoviedb.org": allPublicKeys ] } func build() -> Manager { var serverTrustPolicyManager: ServerTrustPolicyManager? if let policies = self.policies { serverTrustPolicyManager = ServerTrustPolicyManager(policies: policies) } let manager = Manager(configuration: configuration, serverTrustPolicyManager: serverTrustPolicyManager) manager.startRequestsImmediately = false return manager } } Alamofire Implementation
  8. import Moya class MovieServiceImpl: MovieService { private let provider: MoyaProvider<MovieRouter>

    init(provider: MoyaProvider<MovieRouter> = MoyaProvider<MovieRouter>(manager: AlamofireSessionManagerBuilder().build())) { self.provider = provider } @discardableResult func search(query: String, completion: @escaping Completion) -> Cancellable { return provider.request(.search(query: query), completion: completion) } } Moya Implementation
  9. import Moya class MovieServiceImpl: MovieService { private let provider: MoyaProvider<MovieRouter>

    init(provider: MoyaProvider<MovieRouter> = MoyaProvider<MovieRouter>(manager: AlamofireSessionManagerBuilder().build())) { self.provider = provider } @discardableResult func search(query: String, completion: @escaping Completion) -> Cancellable { return provider.request(.search(query: query), completion: completion) } } Moya Implementation
  10. class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive

    challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) { if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) { if let serverTrust = challenge.protectionSpace.serverTrust { let isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil) if(isServerTrusted) { if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) { let serverCertificateData = SecCertificateCopyData(serverCertificate) let data = CFDataGetBytePtr(serverCertificateData); let size = CFDataGetLength(serverCertificateData); let cert1 = NSData(bytes: data, length: size) let file_der = Bundle.main.path(forResource: "certificateFile", ofType: "der") if let file = file_der { if let cert2 = NSData(contentsOfFile: file) { if cert1.isEqual(to: cert2 as Data) { completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust)) return } } } } } } } completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil) } } URLSession Implementation
  11. class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive

    challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) { if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) { if let serverTrust = challenge.protectionSpace.serverTrust { let isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil) if(isServerTrusted) { if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) { let serverCertificateData = SecCertificateCopyData(serverCertificate) let data = CFDataGetBytePtr(serverCertificateData); let size = CFDataGetLength(serverCertificateData); let cert1 = NSData(bytes: data, length: size) let file_der = Bundle.main.path(forResource: "certificateFile", ofType: "der") if let file = file_der { if let cert2 = NSData(contentsOfFile: file) { if cert1.isEqual(to: cert2 as Data) { completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust)) return } } } } } } } completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil) } } URLSession Implementation
  12. class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive

    challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) { if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) { if let serverTrust = challenge.protectionSpace.serverTrust { let isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil) if(isServerTrusted) { if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) { let serverCertificateData = SecCertificateCopyData(serverCertificate) let data = CFDataGetBytePtr(serverCertificateData); let size = CFDataGetLength(serverCertificateData); let cert1 = NSData(bytes: data, length: size) let file_der = Bundle.main.path(forResource: "certificateFile", ofType: "der") if let file = file_der { if let cert2 = NSData(contentsOfFile: file) { if cert1.isEqual(to: cert2 as Data) { completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust)) return } } } } } } } completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil) } } URLSession Implementation
  13. class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive

    challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) { if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) { if let serverTrust = challenge.protectionSpace.serverTrust { let isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil) if(isServerTrusted) { if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) { let serverCertificateData = SecCertificateCopyData(serverCertificate) let data = CFDataGetBytePtr(serverCertificateData); let size = CFDataGetLength(serverCertificateData); let cert1 = NSData(bytes: data, length: size) let file_der = Bundle.main.path(forResource: "certificateFile", ofType: "der") if let file = file_der { if let cert2 = NSData(contentsOfFile: file) { if cert1.isEqual(to: cert2 as Data) { completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust)) return } } } } } } } completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil) } } URLSession Implementation
  14. class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive

    challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) { if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) { if let serverTrust = challenge.protectionSpace.serverTrust { let isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil) if(isServerTrusted) { if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) { let serverCertificateData = SecCertificateCopyData(serverCertificate) let data = CFDataGetBytePtr(serverCertificateData); let size = CFDataGetLength(serverCertificateData); let cert1 = NSData(bytes: data, length: size) let file_der = Bundle.main.path(forResource: "certificateFile", ofType: "der") if let file = file_der { if let cert2 = NSData(contentsOfFile: file) { if cert1.isEqual(to: cert2 as Data) { completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust)) return } } } } } } } completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil) } } URLSession Implementation
  15. class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate { func urlSession(_ session: URLSession, didReceive

    challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) { if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) { if let serverTrust = challenge.protectionSpace.serverTrust { let isServerTrusted = SecTrustEvaluateWithError(serverTrust, nil) if(isServerTrusted) { if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) { let serverCertificateData = SecCertificateCopyData(serverCertificate) let data = CFDataGetBytePtr(serverCertificateData); let size = CFDataGetLength(serverCertificateData); let cert1 = NSData(bytes: data, length: size) let file_der = Bundle.main.path(forResource: "certificateFile", ofType: "der") if let file = file_der { if let cert2 = NSData(contentsOfFile: file) { if cert1.isEqual(to: cert2 as Data) { completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust)) return } } } } } } } completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil) } } URLSession Implementation
  16. var guestSessionIDPersistenceKey: String = "guestSessionIDPersistenceKey" var guestSessionID: String? { get

    { guard let sessionID = defaults.string(forKey: guestSessionIDPersistenceKey) else { return nil } return sessionID } set { defaults.set(newValue, forKey: guestSessionIDPersistenceKey) } } private func requestNewSession() { sessionRepository.new { (result) in guard case .success(let session) = result else { return } self.guestSessionID = session.guestSessionId self.guestSessionExpireDate = session.expiresAt } }
  17. var guestSessionIDPersistenceKey: String = "guestSessionIDPersistenceKey" var guestSessionID: String? { get

    { guard let sessionID = defaults.string(forKey: guestSessionIDPersistenceKey) else { return nil } return sessionID } set { defaults.set(newValue, forKey: guestSessionIDPersistenceKey) } } private func requestNewSession() { sessionRepository.new { (result) in guard case .success(let session) = result else { return } self.guestSessionID = session.guestSessionId self.guestSessionExpireDate = session.expiresAt } }
  18. var guestSessionIDPersistenceKey: String = "guestSessionIDPersistenceKey" var guestSessionID: String? { get

    { guard let sessionID = defaults.string(forKey: guestSessionIDPersistenceKey) else { return nil } return sessionID } set { defaults.set(newValue, forKey: guestSessionIDPersistenceKey) } } private func requestNewSession() { sessionRepository.new { (result) in guard case .success(let session) = result else { return } self.guestSessionID = session.guestSessionId self.guestSessionExpireDate = session.expiresAt } }
  19. private let keychainWrapper: KeychainWrapper var guestSessionID: String? { get {

    guard let sessionID = keychainWrapper .string(forKey: guestSessionIDPersistenceKey) else { return nil } return sessionID } set { guard let newValue = newValue else { keychainWrapper.removeObject(forKey: guestSessionIDPersistenceKey) return } keychainWrapper.set(newValue, forKey: guestSessionIDPersistenceKey) } } import SwiftKeychainWrapper
  20. private let keychainWrapper: KeychainWrapper var guestSessionID: String? { get {

    guard let sessionID = keychainWrapper .string(forKey: guestSessionIDPersistenceKey) else { return nil } return sessionID } set { guard let newValue = newValue else { keychainWrapper.removeObject(forKey: guestSessionIDPersistenceKey) return } keychainWrapper.set(newValue, forKey: guestSessionIDPersistenceKey) } } import SwiftKeychainWrapper
  21. private let keychainWrapper: KeychainWrapper var guestSessionID: String? { get {

    guard let sessionID = keychainWrapper .string(forKey: guestSessionIDPersistenceKey) else { return nil } return sessionID } set { guard let newValue = newValue else { keychainWrapper.removeObject(forKey: guestSessionIDPersistenceKey) return } keychainWrapper.set(newValue, forKey: guestSessionIDPersistenceKey) } } import SwiftKeychainWrapper
  22. private let keychainWrapper: KeychainWrapper var guestSessionID: String? { get {

    guard let sessionID = keychainWrapper .string(forKey: guestSessionIDPersistenceKey) else { return nil } return sessionID } set { guard let newValue = newValue else { keychainWrapper.removeObject(forKey: guestSessionIDPersistenceKey) return } keychainWrapper.set(newValue, forKey: guestSessionIDPersistenceKey) } } import SwiftKeychainWrapper
  23. struct Constants { static let baseURL: String = "http://api.themoviedb.org/3" static

    let imageBaseURL: String = "http://image.tmdb.org/t/p/w500" static let apiKEY: String = "bfA26eca3aCe899c51739f5793de0cd4" }
  24. struct Constants { static let baseURL: String = "http://api.themoviedb.org/3" static

    let imageBaseURL: String = "http://image.tmdb.org/t/p/w500" static let apiKEY: String = "bfA26eca3aCe899c51739f5793de0cd4" }
  25. ~/.cocoapods/ keys/ OS X App AppNameKeys.h Keys Values Scrambled bfA26eca3aCe89

    9c51739f5793de0 cd4 Compile Runtime Cocoapods-keys
  26. plugin 'cocoapods-keys', { :project => "AppName", :target => “TargetName", :keys

    => [ "apiKEY" ] } $ gem install cocoapods-keys $ pod install CocoaPods-Keys has detected a keys mismatch for your setup. What is the key for apiKEY "apiKEY" > | import Keys struct Constants { static let keys = TheVulnerableAppKeys() static let baseURL: String = "http://api.themoviedb.org/3" static let imageBaseURL: String = "http://image.tmdb.org/t/p/w500" static let apiKEY: String = keys.apiKEY }
  27. plugin 'cocoapods-keys', { :project => "AppName", :target => “TargetName", :keys

    => [ "apiKEY" ] } $ gem install cocoapods-keys $ pod install CocoaPods-Keys has detected a keys mismatch for your setup. What is the key for apiKEY "apiKEY" > | import Keys struct Constants { static let keys = TheVulnerableAppKeys() static let baseURL: String = "http://api.themoviedb.org/3" static let imageBaseURL: String = "http://image.tmdb.org/t/p/w500" static let apiKEY: String = keys.apiKEY }
  28. plugin 'cocoapods-keys', { :project => "AppName", :target => “TargetName", :keys

    => [ "apiKEY" ] } $ gem install cocoapods-keys $ pod install CocoaPods-Keys has detected a keys mismatch for your setup. What is the key for apiKEY "apiKEY" > | import Keys struct Constants { static let keys = TheVulnerableAppKeys() static let baseURL: String = "http://api.themoviedb.org/3" static let imageBaseURL: String = "http://image.tmdb.org/t/p/w500" static let apiKEY: String = keys.apiKEY }
  29. plugin 'cocoapods-keys', { :project => "AppName", :target => “TargetName", :keys

    => [ "apiKEY" ] } $ gem install cocoapods-keys $ pod install CocoaPods-Keys has detected a keys mismatch for your setup. What is the key for apiKEY "apiKEY" > | import Keys struct Constants { static let keys = TheVulnerableAppKeys() static let baseURL: String = "http://api.themoviedb.org/3" static let imageBaseURL: String = "http://image.tmdb.org/t/p/w500" static let apiKEY: String = keys.apiKEY }
  30. plugin 'cocoapods-keys', { :project => "AppName", :target => “TargetName", :keys

    => [ "apiKEY" ] } $ gem install cocoapods-keys $ pod install CocoaPods-Keys has detected a keys mismatch for your setup. What is the key for apiKEY "apiKEY" > | import Keys struct Constants { static let keys = TheVulnerableAppKeys() static let baseURL: String = "http://api.themoviedb.org/3" static let imageBaseURL: String = "http://image.tmdb.org/t/p/w500" static let apiKEY: String = keys.apiKEY }
  31. plugin 'cocoapods-keys', { :project => "AppName", :target => “TargetName", :keys

    => [ "apiKEY" ] } $ gem install cocoapods-keys $ pod install CocoaPods-Keys has detected a keys mismatch for your setup. What is the key for apiKEY "apiKEY" > | import Keys struct Constants { static let keys = TheVulnerableAppKeys() static let baseURL: String = "http://api.themoviedb.org/3" static let imageBaseURL: String = "http://image.tmdb.org/t/p/w500" static let apiKEY: String = keys.apiKEY }
  32. Open Web Application Security Project M1 - Improper Platform Usage

    M2 - Insecure Data Storage M3 - Insecure Communication M4 - Insecure Authentication M5 - Insufficient Cryptography M6 - Insecure Authorization M7 - Client Code Quality M8 - Code Tampering M9 - Reverse Engineering M10 - Extraneous Functionality
  33. Thank you! Rodrigo Longhi Guimarães Want to learn more? OWASP

    - Mobile Top 10 Apple - Security Guide github.com/RodrigoLGuimaraes/TheVulnerableApp Charles Lima careers.ebanx.com