Secure Coding in Swift 4

Van het minimaliseren van het gebruik van de aanwijzer tot een sterke typecontrole tijdens het compileren, Swift is een geweldige taal voor veilige ontwikkeling. Maar dat betekent dat het verleidelijk is om de veiligheid helemaal te vergeten. Er zijn nog steeds kwetsbaarheden en Swift is ook aantrekkelijk voor nieuwe ontwikkelaars die nog niet over beveiliging hebben geleerd. 

Deze zelfstudie is een veilige coderingsgids die veranderingen in Swift 4 en de nieuwe toolingopties die beschikbaar zijn in Xcode 9 aanpakt en waarmee u beveiligingskwetsuren kunt verminderen.

Aanwijzingen en overlopen

Veel beveiligingsrisico's hebben betrekking op C en het gebruik van pointers. Dit komt omdat aanwijzers u toegang geven tot onbewerkte geheugenlocaties, waardoor u gemakkelijker kunt lezen en naar het verkeerde gebied kunt schrijven. Het was een belangrijke manier voor aanvallers om een ​​programma kwaadwillig te veranderen. 

Swift maakt meestal geen gebruik van pointers, maar het stelt je wel in staat om met C. te communiceren. Veel API's, inclusief de volledige Core Foundation API van Apple, zijn volledig gebaseerd op C, dus het is heel gemakkelijk om het gebruik van pointers terug in Swift te introduceren.. 

Gelukkig heeft Apple de pointertypen op de juiste manier benoemd: UnsafePointer, UnsafeRawPointerUnsafeBufferPointer, en UnsafeRawBufferPointer. Er komt een moment waarop de API waarmee u communiceert deze typen retourneert en de hoofdregel bij het gebruik ervan is Bewaar of retourneer geen aanwijzingen voor later gebruik. Bijvoorbeeld:

laat myString = "Hallo wereld!" var unsafePointer: UnsafePointer? = nul myString.withCString myStringPointer in unsafePointer = myStringPointer // ergens later ... print (unsafePointer? .pointee)

Omdat we de aanwijzer buiten de sluiting hebben geopend, weten we niet zeker of de aanwijzer nog steeds naar de verwachte geheugeninhoud wijst. De veilige manier om de aanwijzer in dit voorbeeld te gebruiken, is om deze, samen met de afdrukinstructie, binnen de sluiting te houden. 

Aanwijzers naar strings en arrays hebben ook geen controle van grenzen. Dit betekent dat het gemakkelijk is om een ​​onveilige aanwijzer op een array te gebruiken, maar dat u per ongeluk toegang hebt tot over de grens - een bufferoverloop.

var numbers = [1, 2, 3, 4, 5] numbers.withUnsafeMutableBufferPointer buffer in // ok buffer [0] = 5 print (buffer [0]) // slechte buffer [5] = 0 print (buffer [5 ])

Het goede nieuws is dat Swift 4 probeert de app te laten crashen in plaats van door te gaan met wat zou worden genoemd ongedefinieerd gedrag. We weten niet wat buffer [5] wijst naar! Swift zal echter niet elke zaak vangen. Stel een breekpunt in na de volgende code en bekijk variabelen een en c. Ze zullen worden ingesteld op 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 (to: 999) pointer.predecessor (). initialize (to: 999)

Dit demonstreert een stapel overloop omdat zonder een expliciete toewijzing, variabelen over het algemeen op de stapel worden opgeslagen. 

In het volgende voorbeeld maken we een toewijzing met een capaciteit van slechts één int8. De toewijzingen worden op de hoop opgeslagen, dus de volgende regel zal de heap overlopen. Voor dit voorbeeld waarschuwt Xcode u alleen met een notitie in de console die krijgt is onveilig.

let buffer = UnsafeMutablePointer.toewijzen (capaciteit: 1) krijgt (buffer) 

Dus wat is de beste manier om overlopen te voorkomen? Het is uiterst belangrijk om bij het werken met C grenzen te stellen aan de invoer om er zeker van te zijn dat deze binnen bereik is. 

Je denkt misschien dat het vrij moeilijk is om te onthouden en alle verschillende gevallen te vinden. Dus om u te helpen, komt Xcode met een zeer nuttige tool genaamd Sanitizer. 

Address Sanitizer is verbeterd in Xcode 9. Het is een hulpmiddel dat u helpt ongeldige geheugentoegang te krijgen, zoals de voorbeelden die we zojuist hebben gezien. Als je gaat werken met de Onveilig* typen, is het een goed idee om het hulpprogramma Adres Sanitizer te gebruiken. Het is niet standaard ingeschakeld, dus ga naar om het in te schakelen Product> Schema> Wijzigingsschema> Diagnostiek, en check Adres Sanitizer. In Xcode 9 is er een nieuwe suboptie, Detecteer het gebruik van stapel na terugkeer. Deze nieuwe optie detecteert de vulnerabilities use-after-scope en use-after-return van ons eerste voorbeeld.

Soms over het hoofd gezien is de integer overflow. De reden hiervoor is dat integer-overlopen alleen beveiligingsgaten zijn wanneer ze worden gebruikt als een index of als grootte van een buffer, of als de onverwachte waarde van de overloop de stroom kritieke beveiligingscode verandert. Swift 4 haalt de meest voor de hand liggende integer overflows op tijdens compilatie, zoals wanneer het aantal duidelijk groter is dan de maximale waarde van het gehele getal. 

Het volgende compileert bijvoorbeeld niet.

var someInteger: CInt = 2147483647 someInteger + = 1

Maar veel van de keren dat het nummer tijdens runtime dynamisch aankomt, zoals wanneer een gebruiker informatie invoert in a UITextField. Undefined Behavior Sanitizer is een nieuw hulpmiddel in Xcode 9 dat ondertekende integer-overflow en andere bug-bugs detecteert. Ga naar om het in te schakelen Product> Schema> Wijzigingsschema> Diagnostiek, en schakel aan Ongedefinieerd gedrag Sanitizer. Dan in Bouw instellingen> Undefined Behavior Sanitizer, reeks Schakel extra gehele getallen in naar Ja.

Er is nog iets dat het vermelden waard is van ongedefinieerd gedrag. Hoewel pure Swift pointers verbergt, worden referenties en kopieën van buffers nog steeds achter de schermen gebruikt, dus het is mogelijk om tegen gedrag aan te lopen dat je niet had verwacht. Als u bijvoorbeeld begint met itereren over verzamelingsindexen, kunnen de indices per ongeluk door u worden gewijzigd tijdens iteratie.

var-getallen = [1, 2, 3] voor getallen in cijfers aantal afgedrukte (aantal) nummers = [4, 5, 6] //<- accident ???  for number in numbers  print(number) 

Hier hebben we de oorzaak van getallen array om naar een nieuwe array binnen de lus te wijzen. Wat doet dat dan aantal wijzen naar? Dit zou normaal gesproken een bungelende referentie worden genoemd, maar in dit geval maakt Swift impliciet een verwijzing naar een kopie van de buffer van je array gedurende de duur van de lus. Dat betekent dat de printverklaring daadwerkelijk 1, 2 en 3 zal afdrukken in plaats van 1, 4, 5 ... Dit is goed! Swift bespaart u van ongedefinieerd gedrag of een crash van een app, hoewel u die uitvoer misschien ook niet had verwacht. Uw peer-ontwikkelaars verwachten niet dat uw verzameling wordt gemuteerd tijdens de opsomming, dus in het algemeen moet u tijdens het opsommingen extra voorzichtig zijn dat u de verzameling niet wijzigt.

Dus Swift 4 heeft tijdens de compilatie een groot handhavingsniveau voor de beveiliging om deze beveiligingsproblemen op te vangen. Er zijn veel situaties waarin het beveiligingslek pas optreedt als de gebruiker interactie heeft. Swift bevat ook dynamische controle, die ook tijdens runtime veel van de problemen kan opvangen, maar het is te duur om over threads te doen, dus het wordt niet uitgevoerd voor multithread-code. Dynamische controle zal veel maar niet alle overtredingen bevatten, dus het is nog steeds belangrijk om in de eerste plaats beveiligde code te schrijven! 

Laten we daarmee kijken naar een ander heel gemeenschappelijk gebied voor kwetsbaarheden - code-injectie-aanvallen.

Injectie- en formaatreeksaanvallen

Opmaakreeksaanvallen vinden plaats wanneer een invoerreeks in uw app wordt geparseerd als een opdracht die u niet hebt bedoeld. Hoewel pure Swift-reeksen niet vatbaar zijn voor het opmaken van reeksaanvallen, is de doelstelling-C NSString en Core Foundation CFString klassen zijn, en ze zijn verkrijgbaar bij Swift. Beide klassen hebben methoden zoals stringWithFormat.

Laten we zeggen dat de gebruiker willekeurige tekst van een kan invoeren UITextField.

laat inputString = "String uit een tekstveld% @% d% p% ld% @% @" als NSString

Dit kan een beveiligingslek zijn als de formatreeks direct wordt afgehandeld.

laat textFieldString = NSString.init (format: inputString) // bad laat textFieldString = NSString.init (formaat: "% @", inputString) // goed

Swift 4 probeert om stringargumenten voor ontbrekend formaat te verwerken door 0 of NULL te retourneren, maar het is vooral een zorg als de string wordt doorgegeven aan de Objective-C runtime.

NSLog (textFieldString); // bad NSLog ("% @", textFieldString); //goed

Hoewel de onjuiste manier meestal gewoon een crash veroorzaakt, kan een aanvaller voorzichtig een opmaakreeks maken om gegevens te schrijven naar specifieke geheugenlocaties op de stapel om het gedrag van uw app te wijzigen (zoals het wijzigen van een isAuthenticated variable). 

Een andere grote boosdoener is NSPredicate, die een opmaakstring kan accepteren die wordt gebruikt om op te geven welke gegevens worden opgehaald uit Core Data. Clausules zoals NET ZOALS en BEVAT jokertekens toestaan ​​en moeten worden vermeden of op zijn minst alleen voor zoekopdrachten worden gebruikt. Het idee is om opsomming van accounts te voorkomen, bijvoorbeeld wanneer de aanvaller "a *" als accountnaam invoert. Als u de NET ZOALS clausule aan ==, dit betekent dat de string letterlijk moet overeenkomen met "a *". 

Andere veel voorkomende aanvallen gebeuren door de invoerreeks vroegtijdig te beëindigen met een enkel aanhalingsteken, zodat er extra opdrachten kunnen worden ingevoerd. Een login kan bijvoorbeeld worden omzeild door in te voeren ') OF 1 = 1 OF (wachtwoord LIKE' * in de UITextField. Die regel vertaalt zich naar "waar het wachtwoord is zoals alles", wat de authenticatie helemaal omzeilt.De oplossing is om volledig te ontsnappen aan pogingen tot injectie door uw eigen dubbele aanhalingstekens in code toe te voegen.Zo worden eventuele extra aanhalingstekens van de gebruiker als onderdeel gezien van de invoerreeks in plaats van een speciaal terminating-teken te zijn:

laat zoekopdracht = NSPredicate.init (formaat: "wachtwoord == \"% @ \ "", naam)

Nog een manier om je te beschermen tegen deze aanvallen, is door simpelweg te zoeken naar specifieke tekens en deze uit te sluiten waarvan je weet dat ze schadelijk kunnen zijn in de reeks. Voorbeelden hiervan zijn citaten, of zelfs punten en schuine strepen. Het is bijvoorbeeld mogelijk om een map traversal attack wanneer invoer direct wordt doorgegeven aan de Bestandsbeheer klasse. In dit voorbeeld voert de gebruiker "... /" in om de bovenliggende directory van het pad te bekijken in plaats van de bedoelde submap.

laat userControllerString = "... /" als NSString laten sourcePath = NSString.init (formaat: "% @ /% @", Bundle.main.resourcePath!, userControllerString) NSLog ("% @", sourcePath) // In plaats van Build / Producten / Debug / Swift4.app / Inhoud / Bronnen, het wordt Build / Products / Debug / Swift4.app / Inhoud laat bestandsbeheer: FileManager = FileManager () laat files = filemanager.enumerator (atPath: sourcePath as String) terwijl bestand laten = bestanden? .nextObject () print (file)

Andere speciale tekens kunnen een NULL afsluitende byte bevatten als de tekenreeks wordt gebruikt als een C-reeks. Aanwijzers naar C-strings vereisen een NULL afsluitende byte. Vanwege dit is het mogelijk om de string eenvoudig te manipuleren door een NULL-byte te introduceren. De aanvaller kan de reeks eerder willen beëindigen als er een vlag zoals is needs_auth = 1, of wanneer toegang standaard is ingeschakeld en expliciet is uitgeschakeld, bijvoorbeeld met is_subscriber = 0.

laat userInputString = "gebruikersnaam = Ralph \ 0" als NSString laten commandString = NSString.init (formaat: "subscribe_user:% @ & needs_authorization = 1", userInputString) NSLog ("% s", commandString.utf8String!) // prints subscribe_user: gebruikersnaam = Ralph in plaats van subscribe_user: gebruikersnaam = Ralph & needs_authorization = 1

Het parseren van HTML-, XML- en JSON-reeksen vereist ook speciale aandacht. De veiligste manier om met ze te werken is om de native bibliotheken van Foundation te gebruiken die objecten voor elk knooppunt bieden, zoals de NSXMLParser klasse. Swift 4 introduceert typeveilige serialisatie naar externe formaten zoals JSON. Maar als u XML of HTML leest met een aangepast systeem, zorg er dan voor dat speciale tekens uit de gebruikersinvoer niet kunnen worden gebruikt om de tolk te instrueren.

  • < moet worden & lt.
  • > moet vervangen worden door & gt.
  • & zou moeten worden & amp.
  • Binnen attribuutwaarden, elke of ' moet worden & quot en & apos, respectievelijk.

Hier is een voorbeeld van een snelle manier om specifieke tekens te verwijderen of te vervangen:

var myString = "string to sanitize;" myString = myString.replacingOccurrences (of: ";", with: "")

Een laatste gebied voor injectie-aanvallen zijn interne URL-handlers. Controleer of gebruikersinvoer niet rechtstreeks in de aangepaste URL-handlers wordt gebruikt Open url en didReceiveRemoteNotification. Controleer of de URL is wat u verwacht en dat het een gebruiker niet toestaat om willekeurig informatie in te voeren om uw logica te manipuleren. In plaats van de gebruiker bijvoorbeeld te laten kiezen op welk scherm in de stapel indexen worden weergegeven, moet u alleen specifieke schermen toestaan ​​met een ondoorzichtig ID, zoals t = es84jg5urw

Als je gebruikt WKWebViews in uw app, is het misschien goed om de URL's te controleren die daar ook worden geladen. Je kunt negeren decisionPolicyFor navigationAction, waarmee je kunt kiezen of je door wilt gaan met de URL-aanvraag. 

Enkele bekende webviewtrucs omvatten het laden van aangepaste URL-schema's die de ontwikkelaar niet van plan was, zoals een app-id: om een ​​geheel andere app te starten of sms: om een ​​tekst te verzenden. Houd er rekening mee dat ingesloten webweergaven geen balk weergeven met het URL-adres of de SSL-status (het vergrendelingspictogram), zodat de gebruiker niet kan bepalen of de verbinding wordt vertrouwd. 

Als de webview bijvoorbeeld op volledig scherm wordt weergegeven, kan de URL worden gekaapt met een webpagina die er net zo uitziet als uw inlogscherm, maar in plaats daarvan de inloggegevens naar een schadelijk domein leidt. Andere aanvallen in het verleden omvatten cross-site scripting-aanvallen waarbij cookies zijn gelekt en zelfs het volledige bestandssysteem. 

De beste preventie voor alle genoemde aanvallen is om de tijd te nemen om uw interface te ontwerpen met behulp van native UI-besturingselementen in plaats van alleen een webversie in uw app weer te geven.

Tot nu toe hebben we relatief eenvoudige aanvallen bekeken. Maar laten we eindigen met een geavanceerdere aanval die tijdens de runtime kan plaatsvinden.

Runtime-hacking

Net zoals Swift kwetsbaarder wordt wanneer u een interface met C hebt, brengt de interface met Objective-C aparte kwetsbaarheden naar de tafel. 

We hebben de problemen al gezien NSString en tekenreeksaanvallen formatteren. Een ander punt is dat Objective-C veel dynamischer is als taal, waardoor losse typen en methoden kunnen worden doorgegeven. Als je Swift-klasse erft van NSObject, dan wordt het open voor Objective-C runtime-aanvallen. 

De meest voorkomende kwetsbaarheid betreft het dynamisch verwisselen van een belangrijke beveiligingsmethode voor een andere methode. Een methode die bijvoorbeeld terugkeert als een gebruiker is gevalideerd, kan worden omgewisseld voor een andere methode die bijna altijd true retourneert, zoals isRetinaDisplay. Als u het gebruik van Objective-C minimaliseert, wordt uw app robuuster tegen dit type aanval.

In Swift 4 worden methoden op klassen die overerven van een Objective-C-klasse alleen blootgesteld aan de runtime Objective-C als die methoden of de klassen zelf zijn gemarkeerd met @attribuut. Vaak wordt de Swift-functie genoemd, zelfs als de @objc attribuut gebruikt. Dit kan gebeuren als de methode een heeft @objc attribuut maar wordt nooit daadwerkelijk aangeroepen vanuit Objective-C. 

Met andere woorden, Swift 4 introduceert minder @objc gevolgtrekking, dus dit beperkt het aanvalsoppervlak in vergelijking met eerdere versies. Nog steeds, om de runtime-functies te ondersteunen, moeten binairen op basis van doelstelling C veel klasse-informatie bevatten die niet kan worden verwijderd. Dit is genoeg voor reverse engineers om de klasse-interface opnieuw te bouwen om erachter te komen welke beveiligingssecties moeten worden gepatcht. 

In Swift is minder informatie zichtbaar in het binaire bestand en worden functienamen verminkt. Het mangelen kan echter ongedaan worden gemaakt door het Xcode-gereedschap snel te ontwarren. Swift-functies hebben feitelijk een consistent schema voor naamgeving, waarmee wordt aangegeven of elk een Swift-functie is of niet, een deel van een klasse, modulenaam en -lengte, naam en lengte van de klas, naam en lengte van de methode, kenmerken, parameters en retourtype. 

Deze namen zijn korter in Swift 4. Als u zich zorgen maakt over reverse engineering, moet u ervoor zorgen dat de releaseversie van uw app-strippen door te gaan naar Bouw instellingen> Implementatie> Strip Swift-symbolen en de optie instellen op Ja.

Naast het verhullen van kritieke beveiligingscode, kunt u ook vragen om inline te zijn. Dit betekent dat elke plaats waar de functie in uw code wordt aangeroepen, de code op die plaats zal worden herhaald in plaats van alleen op één locatie van het binaire bestand. 

Op deze manier, als een aanvaller een bepaalde beveiligingscontrole weet te omzeilen, heeft dit geen invloed op andere gevallen van die controle op andere plaatsen in uw code. Elke cheque moet worden gepatcht of gehaakt, waardoor het veel moeilijker wordt om met succes een crack uit te voeren. U kunt code inline als volgt:

@inline (__ always) func myFunction () // ...

Conclusie

Denken aan veiligheid zou een groot deel van ontwikkeling moeten zijn. Alleen verwachten dat de taal veilig is, kan leiden tot kwetsbaarheden die voorkomen hadden kunnen worden. Swift is populair voor iOS-ontwikkeling, maar het is beschikbaar voor macOS desktop-apps, tvOS, watchOS en Linux (dus je zou het kunnen gebruiken voor server-side componenten waar het potentieel voor het uitvoeren van code-executies veel groter is). App-sandboxing kan worden verbroken, zoals in het geval van jailbreak-apparaten waardoor niet-ondertekende code kan worden uitgevoerd, dus het is belangrijk om nog steeds na te denken over beveiliging en aandacht te besteden aan Xcode-meldingen terwijl u debugt. 

Een laatste tip is om compilerwaarschuwingen als fouten te behandelen. Je kunt Xcode dwingen om dit te doen door naar te gaan Bouw instellingen en instellen Waarschuwingen als fouten behandelen naar Ja. Vergeet niet om uw projectinstellingen te moderniseren bij het migreren naar Xcode 9 om verbeterde waarschuwingen te krijgen, en last but not least, gebruik de nieuwe functies die beschikbaar zijn door vandaag Swift 4 te gebruiken!

Leer Swift

We hebben een complete gids samengesteld om je te helpen snel te leren, of je nu net begint met de basis of als je meer geavanceerde onderwerpen wilt verkennen.

Voor een inleiding over andere aspecten van veilige codering voor iOS, bekijk enkele van mijn andere berichten hier op Envato Tuts+!