Sicherheitsleitfaden zur Deserialisierung in Swift
Sam Sanoop
18. Juli 2023
0 Min. LesezeitBei der Deserialisierung werden Daten aus einem serialisierten Format, etwa JSON oder Binärdaten, in ihre ursprüngliche Form zurückgeführt. Swift bietet mehrere Protokolle, mit denen sich Objekte und Werte in Property Lists, JSON und andere flache Binärformate umwandeln und daraus zurückführen lassen.
Deserialisierung kann in der Codebasis auch unbemerkt Sicherheitsschwachstellen verursachen, die Angreifer ausnutzen könnten. Dieser Blogbeitrag erläutert Deserialisierungsschwachstellen in Swift, die bei Verwendung der verbreiteten APIs NSCoding und NSSecureCoding auftreten können, und zeigt, wie Sie sie wirksam verhindern.
NSCoding
NSCoding ist ein von Apple bereitgestelltes Protokoll, das sich zur Serialisierung und Deserialisierung von Daten verwenden lässt. Das standardmäßige Protokoll zur Serialisierung und Deserialisierung von Daten mit NSCoding ist anfällig für Objekt-Ersetzungsangriffe, durch die Angreifer unter Umständen Remote-Codeausführung erreichen können.
Betrachten Sie die folgende Klasse, die NSCoding mit zwei Methoden implementiert: initWithCoder: und encodeWithCoder:. Klassen, die NSCoding entsprechen, können in Daten serialisiert und deserialisiert werden, die sich auf einem Datenträger archivieren oder über ein Netzwerk übertragen lassen.
Die obige Klasse entspricht dem NSCoding-Protokoll und verfügt über zwei Eigenschaften: name und role. Außerdem implementiert die Klasse den Initialisierer required init(coder:), um die Eigenschaften name und role aus einem Archiv zu dekodieren, sowie die Methode encode(with:), um diese Eigenschaften in einem Archiv zu kodieren.
Anschließend lässt sich ein Mitarbeiterobjekt erstellen und mithilfe von NSKeyedArchiver in einer Datei archivieren.
Anschließend lässt sich das Objekt mithilfe von NSKeyedUnarchiver aus der Datei wieder in ein Mitarbeiterobjekt deserialisieren, um die Werte der Eigenschaften name und role auszugeben.
Dies lässt sich potenziell wie folgt ausnutzen. Nehmen wir an, dass in der Codebasis weitere Klassen existieren, darunter die folgende. Diese Klasse hat eine einzelne Eigenschaft namens "command" – eine Zeichenfolge, die einen Dateipfad darstellt. Außerdem verfügt sie über die beiden Methoden "encode(with:)" und "init?(coder:)", die für die Konformität mit dem NSCoding-Protokoll erforderlich sind.
Die Eigenschaft command stammt aus einem kodierten Objekt und wird an die Funktion sink1 weitergegeben, die die Eigenschaft als Teil eines Befehls ausführt.
Ein Angreifer kann diese Klasse ausnutzen, um Code auszuführen und einen Deserialisierungsangriff durchzuführen, der zu einer Befehlsinjektion führen kann.
NSSecureCoding
NSSecureCoding ist eine sichere Alternative zu NSCoding und bietet erweiterte Sicherheitsfunktionen zum Schutz vor Deserialisierungsangriffen. Anders als NSCoding stellt NSSecureCoding strengere Sicherheitsanforderungen an kodierte und dekodierte Objekte. Dazu gehören eine festgelegte Klassenhierarchie und bestimmte Sicherheitsmaßnahmen, die in den Klassen selbst implementiert werden müssen. Diese zusätzlichen Maßnahmen tragen dazu bei, die Manipulation serialisierter Daten zu verhindern, durch die Objekte erstellt werden könnten, die schädlichen Code ausführen. So wird die Gesamtsicherheit des Systems erhöht. Je nach Verwendung kann es jedoch weiterhin zu Deserialisierungsangriffen kommen.
Dekodierung ohne Überprüfung des Klassentyps
Im folgenden Beispiel wurde der Code so geändert, dass er NSSecureCoding entspricht. Die Eigenschaft supportSecureCoding wurde auf true gesetzt, und wie zuvor wird die Methode decodeObject zum Deserialisieren kodierter Objekte verwendet.
Wie jedoch in der Apple-Entwicklerdokumentation zu NSSecureCoding erläutert, ist dieses Vorgehen potenziell unsicher: Wenn Sie den Klassentyp überprüfen, wurde das Objekt bereits erstellt und – falls es Teil einer Sammlungsklasse ist – möglicherweise schon in einen Objektgraphen eingefügt. Mit supportsSecureCoding auf true wird ein Objekt als sicher zu dekodieren gekennzeichnet. NSSecureCoding überprüft jedoch weder den Typ dieses Objekts noch, ob es zur betreffenden Mitarbeiterklasse gehört.
Um dieses Problem zu beheben, können Sie den Schlüssel of mit decodeObject(of:Employee.self, forKey: "name") angeben. Dadurch werden nur Objekte der angegebenen Klasse dekodiert (in diesem Fall Employee). So wird sichergestellt, dass die Eigenschaft name nur als Zeichenfolge und nicht als manipuliertes Objekt dekodiert werden kann. Alternativ lässt sich die Methode decodeObjectOfClass verwenden, die ein Objekt für den Schlüssel ausschließlich dann dekodiert, wenn es der angegebenen Klasse entspricht. Außerdem sollten Sie die Methoden decodeObjectForKey und decodeTopLevelObjectForKey nicht verwenden: Auch sie sind von diesem Problem betroffen und gelten laut Apple als veraltet.
supportsSecureCoding auf False gesetzt
Wie in der Apple-Entwickler-Dokumentation erläutert, sollten Sie beim Schreiben einer Klasse, die sicheres Coding unterstützt, sicherstellen, dass der Getter dieser Klasseneigenschaft true zurückgibt. Wenn Sie supportsSecureCoding = false setzen, entspricht die Klasse zwar weiterhin NSSecureCoding. Das vermittelt Entwicklerinnen und Entwicklern jedoch ein falsches Sicherheitsgefühl und ermöglicht zugleich Deserialisierungsangriffe.
Sichere Swift-Deserialisierung
Zusammenfassend sollten Sie Sicherheitsaspekte berücksichtigen, wenn Sie Benutzerdaten mit NSCoding und NSSecureCoding deserialisieren. Die Dokumentation erweckt möglicherweise den Eindruck, NSSecureCoding verhindere Deserialisierung standardmäßig. Das ist jedoch nicht der Fall: Auch mit NSSecureCoding kann es zu unsicherer Deserialisierung kommen. Wenn Sie Dekodierfunktionen mit NSSecureCoding verwenden, müssen Sie stets den Typ des zu deserialisierenden Objekts überprüfen.
Referenzen
Den Beispielcode aus diesem Blogbeitrag finden Sie hier: https://github.com/snoopysecurity/swift-deserialization-security-primer



