This reverts commit c2c270feef
.
7.4 KiB
iOS Serialisation and Encoding
NSCoding and NSSecureCoding
iOS comes with two protocols for object serialisation for Objective-C or NSObject
s: NSCoding
and NSSecureCoding
. When a class conforms to either of the protocols, the data is serialized to NSData
: a wrapper for byte buffers. Note that Data
in Swift is the same as NSData
or its mutable counterpart: NSMutableData
. The NSCoding
protocol declares the two methods that must be implemented in order to encode/decode its instance-variables. A class using NSCoding
needs to implement NSObject
or be annotated as an @objc class. The NSCoding
protocol requires to implement encode and init as shown below.
class CustomPoint: NSObject, NSCoding {
//required by NSCoding:
func encode(with aCoder: NSCoder) {
aCoder.encode(x, forKey: "x")
aCoder.encode(name, forKey: "name")
}
var x: Double = 0.0
var name: String = ""
init(x: Double, name: String) {
self.x = x
self.name = name
}
// required by NSCoding: initialize members using a decoder.
required convenience init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: "name") as? String
else {return nil}
self.init(x:aDecoder.decodeDouble(forKey:"x"),
name:name)
}
//getters/setters/etc.
}
The issue with NSCoding
is that the object is often already constructed and inserted before you can evaluate the class-type. This allows an attacker to easily inject all sorts of data. Therefore, the NSSecureCoding
protocol has been introduced. When conforming to NSSecureCoding
you need to include:
static var supportsSecureCoding: Bool {
return true
}
when init(coder:)
is part of the class. Next, when decoding the object, a check should be made, e.g.:
let obj = decoder.decodeObject(of:MyClass.self, forKey: "myKey")
The conformance to NSSecureCoding
ensures that objects being instantiated are indeed the ones that were expected. However, there are no additional integrity checks done over the data and the data is not encrypted. Therefore, any secret data needs additional encryption and data of which the integrity must be protected, should get an additional HMAC.
Object Archiving with NSKeyedArchiver
NSKeyedArchiver
is a concrete subclass of NSCoder
and provides a way to encode objects and store them in a file. The NSKeyedUnarchiver
decodes the data and recreates the original data. Let's take the example of the NSCoding
section and now archive and unarchive them:
// archiving:
NSKeyedArchiver.archiveRootObject(customPoint, toFile: "/path/to/archive")
// unarchiving:
guard let customPoint = NSKeyedUnarchiver.unarchiveObjectWithFile("/path/to/archive") as?
CustomPoint else { return nil }
You can also save the info in primary plist NSUserDefaults
:
// archiving:
let data = NSKeyedArchiver.archivedDataWithRootObject(customPoint)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "customPoint")
// unarchiving:
if let data = NSUserDefaults.standardUserDefaults().objectForKey("customPoint") as? NSData {
let customPoint = NSKeyedUnarchiver.unarchiveObjectWithData(data)
}
Codable
It is a combination of the Decodable
and Encodable
protocols. A String
, Int
, Double
, Date
, Data
and URL
are Codable
by nature: meaning they can easily be encoded and decoded without any additional work. Let's take the following example:
struct CustomPointStruct:Codable {
var x: Double
var name: String
}
By adding Codable
to the inheritance list for the CustomPointStruct
in the example, the methods init(from:)
and encode(to:)
are automatically supported. Fore more details about the workings of Codable
check the Apple Developer Documentation.
You can also use codable to save the data in the primary property list NSUserDefaults
:
struct CustomPointStruct: Codable {
var point: Double
var name: String
}
var points: [CustomPointStruct] = [
CustomPointStruct(point: 1, name: "test"),
CustomPointStruct(point: 2, name: "test"),
CustomPointStruct(point: 3, name: "test"),
]
UserDefaults.standard.set(try? PropertyListEncoder().encode(points), forKey: "points")
if let data = UserDefaults.standard.value(forKey: "points") as? Data {
let points2 = try? PropertyListDecoder().decode([CustomPointStruct].self, from: data)
}
JSON Encoding
There are a lot of thrid party libraries to encode data in JSON (like exposed here). However, Apple provides support for JSON encoding/decoding directly by combining Codable
together with a JSONEncoder
and a JSONDecoder
:
struct CustomPointStruct: Codable {
var point: Double
var name: String
}
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let test = CustomPointStruct(point: 10, name: "test")
let data = try encoder.encode(test)
let stringData = String(data: data, encoding: .utf8)
// stringData = Optional ({
// "point" : 10,
// "name" : "test"
// })
XML
There are multiple ways to do XML encoding. Similar to JSON parsing, there are various third party libraries, such as: Fuzi, Ono, AEXML, RaptureXML, SwiftyXMLParser, SWXMLHash
They vary in terms of speed, memory usage, object persistence and more important: differ in how they handle XML external entities. See XXE in the Apple iOS Office viewer as an example. Therefore, it is key to disable external entity parsing if possible. See the OWASP XXE prevention cheatsheet for more details. Next to the libraries, you can make use of Apple's XMLParser
class
When not using third party libraries, but Apple's XMLParser
, be sure to let shouldResolveExternalEntities
return false
.
{% hint style="danger" %}
All these ways of serialising/encoding data can be used to store data in the file system. In those scenarios, check if the stored data contains any kind of sensitive information.
Moreover, in some cases you may be able to abuse some serialised data (capturing it via MitM or modifying it inside the filesystem) deserializing arbitrary data and making the application perform unexpected actions (see Deserialization page). In these cases, it's recommended to send/save the serialised data encrypted and signed.
{% endhint %}
References
{% embed url="https://mobile-security.gitbook.io/mobile-security-testing-guide/ios-testing-guide/0x06h-testing-platform-interaction#testing-object-persistence-mstg-platform-8" %}