Dal minimizzare l'uso del puntatore al controllo di tipo forte in fase di compilazione, Swift è un ottimo linguaggio per uno sviluppo sicuro. Ma questo significa che è allettante dimenticare completamente la sicurezza. Ci sono ancora vulnerabilità e Swift attira anche i nuovi sviluppatori che non hanno ancora imparato a conoscere la sicurezza.
Questo tutorial è una guida alla codifica sicura che affronterà le modifiche in Swift 4 e le nuove opzioni di strumenti disponibili in Xcode 9 che ti aiuteranno a mitigare le vulnerabilità della sicurezza.
Molte vulnerabilità della sicurezza ruotano attorno a C e al suo uso di puntatori. Questo perché i puntatori consentono di accedere a posizioni di memoria non formattate, semplificando la lettura e la scrittura nell'area sbagliata. È stato un modo importante per gli aggressori di modificare maliziosamente un programma.
Swift per lo più elimina i puntatori, ma consente comunque di interfacciare con C. Molte API, inclusa l'intera API Core Foundation di Apple, sono interamente basate su C, quindi è molto semplice introdurre nuovamente l'uso dei puntatori in Swift.
Fortunatamente, Apple ha chiamato i tipi di puntatore in modo appropriato: UnsafePointer
, UnsafeRawPointer
, UnsafeBufferPointer
, e UnsafeRawBufferPointer
. Arriverà un momento in cui l'API con cui stai interagendo restituirà questi tipi e la regola principale quando li usi è non memorizzare o restituire puntatori per un uso successivo. Per esempio:
let myString = "Ciao mondo!" var unsafePointer: UnsafePointer? = nil myString.withCString myStringPointer in unsafePointer = myStringPointer // qualche tempo dopo ... print (unsafePointer? .pointee)
Poiché abbiamo acceduto al puntatore al di fuori della chiusura, non sappiamo con certezza se il puntatore punta ancora al contenuto di memoria previsto. Il modo sicuro di usare il puntatore in questo esempio sarebbe di mantenerlo, insieme alla dichiarazione di stampa, all'interno della chiusura.
Anche i puntatori a stringhe e matrici non hanno limiti di verifica. Ciò significa che è facile usare un puntatore non sicuro su un array ma accedervi accidentalmente oltre il limite, un overflow del buffer.
var numbers = [1, 2, 3, 4, 5] numbers.withUnsafeMutableBufferPointer buffer in // ok buffer [0] = 5 print (buffer [0]) // bad buffer [5] = 0 print (buffer [5 ])
La buona notizia è che Swift 4 tenta di bloccare l'app anziché continuare con quello che verrebbe chiamato comportamento indefinito. Non sappiamo cosa tampone [5]
punta a! Tuttavia, Swift non catturerà tutti i casi. Imposta un punto di interruzione dopo il codice seguente e guarda le variabili un
e c
. Saranno impostati su 999
.
func getAddress (pointer: UnsafeMutablePointer) -> UnsafeMutablePointer return pointer var a = 111 var b = 222 var c = 333 let pointer: UnsafeMutablePointer = getAddress (pointer: & b) pointer.successor (). initialize (a: 999) pointer.predecessor (). initialize (a: 999)
Questo dimostra a stack overflow perché senza un'allocazione esplicita, le variabili vengono generalmente memorizzate nello stack.
Nel prossimo esempio, creiamo un'allocazione con una capacità di un singolo int8
. Le allocazioni sono archiviate nell'heap, quindi la riga successiva verrà sovrasfruttata dall'heap. Per questo esempio, Xcode ti avvisa solo con una nota nella console prende
è pericoloso.
let buffer = UnsafeMutablePointer.allocare (capacità: 1) ottiene (buffer)
Quindi qual è il modo migliore per evitare gli overflow? È estremamente importante interagire con C per controllare i limiti sull'input per assicurarsi che sia all'interno del raggio d'azione.
Potresti pensare che sia piuttosto difficile da ricordare e trovare tutti i diversi casi. Quindi, per aiutarti, Xcode ha uno strumento molto utile chiamato Address Sanitizer.
Address Sanitizer è stato migliorato in Xcode 9. È uno strumento che ti aiuta a catturare l'accesso alla memoria non valido come gli esempi che abbiamo appena visto. Se lavorerai con il Unsafe *
tipi, è una buona idea usare lo strumento Address Sanitizer. Non è abilitato di default, quindi per abilitarlo vai su Prodotto> Schema> Modifica schema> Diagnostica, e controllare Indirizzo Sanitizer. In Xcode 9 c'è una nuova sotto-opzione, Rileva l'uso dello stack dopo il ritorno. Questa nuova opzione rileva le vulnerabilità da utilizzo dopo l'uso e ritorno dopo l'uso dal nostro primo esempio.
A volte trascurato è il overflow intero. Questo perché gli overflow integer sono buchi di sicurezza solo quando vengono utilizzati come indice o dimensione di un buffer o se il valore imprevisto dell'overflow cambia il flusso del codice di sicurezza critico. Swift 4 rileva overflow integer più evidenti in fase di compilazione, ad esempio quando il numero è chiaramente più grande del valore massimo del numero intero.
Ad esempio, il seguente non verrà compilato.
var someInteger: CInt = 2147483647 someInteger + = 1
Ma molte volte il numero arriverà dinamicamente in fase di esecuzione, ad esempio quando un utente inserisce le informazioni in a UITextField
. Undefined Behavior Sanitizer è un nuovo strumento in Xcode 9 che rileva un overflow di interi con segno e altri bug di tipo mancata corrispondenza. Per abilitarlo, vai a Prodotto> Schema> Modifica schema> Diagnostica, e accendi Disinfettante comportamento non definito. Quindi in Impostazioni di compilazione> Sanitizer del comportamento non definito, impostato Abilita assegni interi extra a sì.
C'è un'altra cosa che vale la pena di menzionare sul comportamento non definito. Anche se il puro Swift nasconde i puntatori, i riferimenti e le copie dei buffer sono ancora utilizzati dietro le quinte, quindi è possibile imbattersi in comportamenti che non ti aspettavi. Ad esempio, quando inizi a scorrere gli indici di raccolta, gli indici potrebbero essere modificati per errore durante l'iterazione.
var numbers = [1, 2, 3] per numero in numeri stampa (numero) numeri = [4, 5, 6] //<- accident ??? for number in numbers print(number)
Qui abbiamo solo causato il numeri
array per puntare a una nuova matrice all'interno del ciclo. Allora cosa fa numero
indicare? Questo sarebbe normalmente chiamato riferimento ciondolante, ma in questo caso Swift crea implicitamente un riferimento ad una copia del buffer dell'array per la durata del ciclo. Ciò significa che l'istruzione di stampa stamperà effettivamente 1, 2 e 3 invece di 1, 4, 5 ... Questo è buono! Swift ti sta salvando da un comportamento indefinito o da un arresto anomalo dell'app, anche se non ti saresti aspettato quell'output. I tuoi sviluppatori peer non si aspettano che la tua raccolta venga modificata durante l'enumerazione, quindi in generale, fai attenzione durante l'enumerazione che non stai modificando la raccolta.
Quindi Swift 4 ha una grande sicurezza in fase di compilazione per cogliere queste vulnerabilità di sicurezza. Esistono molte situazioni in cui la vulnerabilità non esiste fino al runtime in cui è presente l'interazione dell'utente. Swift include anche il controllo dinamico, che può catturare molti dei problemi anche in fase di esecuzione, ma è troppo costoso da fare attraverso i thread quindi non viene eseguito per il codice multithread. Il controllo dinamico catturerà molte ma non tutte le violazioni, quindi è ancora importante scrivere codice sicuro in primo luogo!
Detto questo, passiamo a un'altra area molto comune per le vulnerabilità: attacchi di iniezione di codice.
Gli attacchi alle stringhe di formato avvengono quando una stringa di input viene analizzata nella tua app come un comando che non hai inteso. Mentre le stringhe di Swift pure non sono suscettibili di formattare gli attacchi di stringa, l'Objective-C NSString
e Core Foundation CFString
le classi sono e sono disponibili da Swift. Entrambe queste classi hanno metodi come stringWithFormat
.
Diciamo che l'utente può inserire un testo arbitrario da a UITextField
.
let inputString = "String da un campo di testo% @% d% p% ld% @% @" come NSString
Questo potrebbe essere un buco di sicurezza se la stringa di formato viene gestita direttamente.
let textFieldString = NSString.init (format: inputString) // bad let textFieldString = NSString.init (formato: "% @", inputString) // buona
Swift 4 prova a gestire gli argomenti delle stringhe di formato mancanti restituendo 0 o NULL, ma è soprattutto un problema se la stringa verrà restituita al runtime Objective-C.
NSLog (textFieldString); // bad NSLog ("% @", textFieldString); //bene
Mentre la maggior parte delle volte il modo sbagliato causa solo un arresto anomalo, un utente malintenzionato può creare con cura una stringa di formato per scrivere i dati in posizioni di memoria specifiche nello stack per alterare il comportamento dell'app (come cambiare un IsAuthenticated
variabile).
Un altro grande colpevole è NSPredicate
, che può accettare una stringa di formato utilizzata per specificare quali dati vengono recuperati dai dati principali. Clausole come PIACE
e CONTIENE
consentire i caratteri jolly e dovrebbe essere evitato, o almeno utilizzato solo per le ricerche. L'idea è di evitare l'enumerazione degli account, ad esempio, laddove l'autore dell'attacco inserisca "a *" come nome dell'account. Se cambi il PIACE
clausola a ==
, questo significa che la stringa deve corrispondere letteralmente a "a *".
Altri attacchi comuni avvengono terminando anticipatamente la stringa di input con un carattere a virgoletta singola, in modo da poter inserire ulteriori comandi. Ad esempio, un accesso potrebbe essere bypassato inserendo ') OR 1 = 1 O (password LIKE' * *
nel UITextField
. Questa linea si traduce in "dove la password è come qualsiasi cosa", che ignora del tutto l'autenticazione.La soluzione è di sfuggire completamente a qualsiasi tentativo di iniezione aggiungendo le proprie virgolette doppie nel codice. In questo modo, qualsiasi citazione aggiuntiva dell'utente viene vista come parte della stringa di input anziché essere un carattere di chiusura speciale:
let query = NSPredicate.init (formato: "password == \"% @ \ "", nome)
Un altro modo per proteggersi da questi attacchi è semplicemente cercare ed escludere caratteri specifici che potrebbero essere dannosi nella stringa. Gli esempi includevano virgolette, o anche punti e barre. Ad esempio, è possibile fare a attacco di directory directory quando l'input viene passato direttamente al FileManager
classe. In questo esempio, l'utente inserisce "... /" per visualizzare la directory padre del percorso anziché la sottodirectory desiderata.
let userControllerString = "... /" come NSString lascia sourcePath = NSString.init (formato: "% @ /% @", Bundle.main.resourcePath!, userControllerString) NSLog ("% @", sourcePath) // Invece di Build / Prodotti / Debug / Swift4.app / Contents / Resources, sarà Build / Products / Debug / Swift4.app / Contents let filemanager: FileManager = FileManager () lascia files = filemanager.enumerator (atPath: sourcePath as String) mentre lascia il file = file? .nextObject () print (file)
Altri caratteri speciali potrebbero includere un byte di terminazione NULL se la stringa viene usata come una stringa C. I puntatori alle stringhe C richiedono un byte di terminazione NULL. Per questo motivo, è possibile manipolare la stringa semplicemente introducendo un byte NULL. L'autore dell'attacco potrebbe voler terminare la stringa in anticipo se esiste una bandiera come needs_auth = 1
, o quando l'accesso è attivo per impostazione predefinita e disattivato in modo esplicito come con is_subscriber = 0
.
let userInputString = "username = Ralph \ 0" come NSString let commandString = NSString.init (formato: "subscribe_user:% @ & needs_authorization = 1", userInputString) NSLog ("% s", commandString.utf8String!) // stampa subscribe_user: username = Ralph invece di subscribe_user: username = Ralph & needs_authorization = 1
L'analisi di stringhe HTML, XML e JSON richiede anche un'attenzione speciale. Il modo più sicuro per lavorare con loro è utilizzare le librerie native di Foundation che forniscono oggetti per ogni nodo, come ad esempio NSXMLParser
classe. Swift 4 introduce la serializzazione sicura per tipo su formati esterni come JSON. Ma se stai leggendo XML o HTML usando un sistema personalizzato, assicurati che caratteri speciali dall'input dell'utente non possano essere usati per istruire l'interprete.
<
deve diventare & lt
.>
dovrebbe essere sostituito con & gt
.&
dovrebbe diventare & amp
.“
o '
bisogno di diventare & quot
e & APOS
, rispettivamente.Ecco un esempio di un modo rapido per rimuovere o sostituire caratteri specifici:
var myString = "stringa per disinfettare;" myString = myString.replacingOccurrences (of: ";", con: "")
Un'area finale per gli attacchi di iniezione è all'interno dei gestori di URL. Verifica che l'input dell'utente non venga utilizzato direttamente all'interno dei gestori di URL personalizzati openURL
e didReceiveRemoteNotification
. Verifica che l'URL sia quello che ti aspetti e che non consenta a un utente di inserire arbitrariamente informazioni per manipolare la tua logica. Ad esempio, invece di lasciare che l'utente scelga in quale schermata dello stack navigare per indice, consenti solo schermate specifiche usando un identificatore opaco, come t = es84jg5urw
.
Se stai usando WKWebView
s nella tua app, potrebbe essere utile controllare gli URL che verranno caricati anche lì. Puoi scavalcare deciderePolicyPer navigazioneAzione
, che ti consente di scegliere se continuare con la richiesta URL.
Alcuni trucchi di Webview noti comprendono il caricamento di schemi URL personalizzati che lo sviluppatore non ha inteso, come ad esempio App-ID:
lanciare un'app completamente diversa o sms:
per inviare un testo. Si noti che le visualizzazioni Web incorporate non mostrano una barra con l'indirizzo URL o lo stato SSL (l'icona di blocco), quindi l'utente non è in grado di determinare se la connessione è affidabile.
Se la visualizzazione Web è a schermo intero, ad esempio, l'URL potrebbe essere dirottato con una pagina web che assomiglia alla schermata di accesso, eccetto invece indirizzare le credenziali a un dominio malevolo. Altri attacchi in passato hanno incluso attacchi di scripting cross-site che hanno fatto trapelare i cookie e persino l'intero filesystem.
La migliore prevenzione per tutti gli attacchi citati consiste nel prendersi il tempo necessario per progettare l'interfaccia utilizzando i controlli dell'interfaccia utente nativa invece di visualizzare semplicemente una versione basata sul Web all'interno della propria app.
Finora, abbiamo esaminato tipi di attacchi relativamente semplici. Ma finiamo con un attacco più avanzato che può accadere nel runtime.
Proprio come Swift diventa più vulnerabile quando si interfaccia con C, l'interfaccia con Objective-C porta vulnerabilità separate alla tabella.
Abbiamo già visto i problemi con NSString
e formattare attacchi a stringa. Un altro punto è che Objective-C è molto più dinamico come linguaggio, permettendo di liberare tipi e metodi liberi. Se la tua classe Swift eredita da NSObject
, quindi diventa aperto agli attacchi runtime Objective-C.
La vulnerabilità più comune comporta lo scambio dinamico di un metodo di sicurezza importante per un altro metodo. Ad esempio, un metodo che restituisce se un utente è convalidato può essere scambiato per un altro metodo che restituirà quasi sempre true, ad esempio isRetinaDisplay
. Ridurre al minimo l'utilizzo di Objective-C renderà la tua app più solida contro questo tipo di attacco.
In Swift 4, i metodi sulle classi che ereditano da una classe Objective-C sono esposti solo al runtime Objective-C se quei metodi o le classi stesse sono contrassegnati con @attributo
. Spesso viene chiamata la funzione Swift, anche se il @objc
l'attributo è usato. Questo può accadere quando il metodo ha un @objc
attributo ma non viene mai effettivamente chiamato da Objective-C.
In altre parole, Swift 4 introduce meno @objc
inferenza, quindi questo limita la superficie di attacco rispetto alle versioni precedenti. Tuttavia, per supportare le funzionalità di runtime, i binari basati su Objective-C devono conservare molte informazioni sulla classe che non possono essere rimosse. Questo è sufficiente per i tecnici inversi per ricostruire l'interfaccia di classe per capire quali sezioni di sicurezza applicare alle patch, ad esempio.
In Swift, ci sono meno informazioni esposte nel file binario e i nomi delle funzioni vengono manomessi. Tuttavia, il mangling può essere annullato dallo strumento Xcode swift-demangle. Infatti, le funzioni di Swift hanno uno schema di denominazione coerente, che indica se ognuna è una funzione Swift o meno, parte di una classe, nome e lunghezza del modulo, nome e lunghezza della classe, nome e lunghezza del metodo, attributi, parametri e tipo di ritorno.
Questi nomi sono più brevi in Swift 4. Se sei preoccupato del reverse engineering, assicurati che la versione di rilascio della tua app rimuova i simboli andando a Crea impostazioni> Distribuzione> Elimina simboli Swift e impostando l'opzione su sì.
Oltre a nascondere il codice di sicurezza critico, puoi anche richiedere che sia in linea. Questo significa che qualsiasi posto in cui la funzione viene chiamata nel tuo codice, il codice verrà ripetuto in quel posto invece di esistere solo in una posizione del binario.
In questo modo, se un utente malintenzionato riesce a ignorare un particolare controllo di sicurezza, non avrà alcun effetto sulle altre occorrenze di quel controllo situate in altre parti del codice. Ogni controllo deve essere rattoppato o agganciato, rendendo molto più difficile eseguire correttamente un crack. Puoi inserire un codice in questo modo:
@inline (__ always) func myFunction () // ...
Pensare alla sicurezza dovrebbe essere una parte importante dello sviluppo. Solo aspettarsi che la lingua sia sicura può portare a vulnerabilità che avrebbero potuto essere evitate. Swift è popolare per lo sviluppo iOS, ma è disponibile per le app desktop macOS, tvOS, watchOS e Linux (quindi è possibile utilizzarlo per componenti lato server dove il potenziale per gli exploit di esecuzione del codice è molto più alto). Il sandbox delle app può essere rotto, come nel caso dei dispositivi jailbroken che consentono l'esecuzione di codice non firmato, quindi è importante pensare ancora alla sicurezza e prestare attenzione alle notifiche Xcode mentre si esegue il debug.
Un ultimo consiglio è di considerare gli avvertimenti del compilatore come errori. Puoi forzare Xcode a farlo andando a Costruisci le impostazioni e impostazione Trattare gli avvertimenti come errori a sì. Non dimenticare di modernizzare le impostazioni del tuo progetto durante la migrazione a Xcode 9 per ricevere avvisi migliorati e, ultimo ma non meno importante, sfrutta le nuove funzionalità disponibili adottando Swift 4 oggi!
Abbiamo creato una guida completa per aiutarti a imparare Swift, sia che tu stia appena iniziando con le basi o che desideri esplorare argomenti più avanzati.
Per un primer su altri aspetti della codifica sicura per iOS, controlla alcuni dei miei altri post qui su Envato Tuts+!