mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-26 14:40:37 +00:00
154 lines
7.4 KiB
Markdown
154 lines
7.4 KiB
Markdown
# 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.
|
|
|
|
```swift
|
|
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`](https://developer.apple.com/documentation/foundation/NSSecureCoding) you need to include:
|
|
|
|
```swift
|
|
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.:
|
|
|
|
```swift
|
|
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:
|
|
|
|
```swift
|
|
// 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`:
|
|
|
|
```swift
|
|
// 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:
|
|
|
|
```swift
|
|
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](https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types).
|
|
|
|
You can also use codable to save the data in the primary property list `NSUserDefaults`:
|
|
|
|
```swift
|
|
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](https://mobile-security.gitbook.io/mobile-security-testing-guide/ios-testing-guide/0x06h-testing-platform-interaction#json-and-codable)\). However, Apple provides support for JSON encoding/decoding directly by combining `Codable` together with a `JSONEncoder` and a `JSONDecoder`:
|
|
|
|
```swift
|
|
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](https://github.com/cezheng/Fuzi), [Ono](https://github.com/mattt/Ono), [AEXML](https://github.com/tadija/AEXML), [RaptureXML](https://github.com/ZaBlanc/RaptureXML), [SwiftyXMLParser](https://github.com/yahoojapan/SwiftyXMLParser), [SWXMLHash](https://github.com/drmohundro/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](https://nvd.nist.gov/vuln/detail/CVE-2015-3784) as an example. Therefore, it is key to disable external entity parsing if possible. See the [OWASP XXE prevention cheatsheet](https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html) for more details. Next to the libraries, you can make use of Apple's [`XMLParser` class](https://developer.apple.com/documentation/foundation/xmlparser)
|
|
|
|
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](../../pentesting-web/deserialization/)\). 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" %}
|
|
|
|
|
|
|