How to test object substitution attacks and NSSecureCoding

I am a Software Engineer working on open source and enterprise mobile SDKs for iOS and MacOS developers written in Swift. From 🇩🇪 and happily living in 🇺🇸
In this blog post I tell you how to test object substitution attacks and how using NSSecureCoding can prevent such attacks. I will also
- share example code on how to use
NSKeyedArchiverandNSKeyedUnArchivereither with or withoutNSSecureCoding. - explain how to archive / unarchive an object into different formats (binary vs. XML).
If you are not too familiar with the idea behind NSSecureCoding when using NSKeyedArchiver / NSKeyedUnarchiver then I recommend that you watch Apple's WWDC 2018 session Data You Can Trust.
Additionally I recommend to read the following articles as those helped me to deepen my understanding.
Object substitution attack
First let's encode data with NSKeyedArchiver in the form of XML.
let archiver = NSKeyedArchiver(requiringSecureCoding: false)
archiver.outputFormat = .xml
archiver.encodeRootObject(yourObject)
archiver.finishEncoding()
let data = archiver.encodedData
archive.write(to: localURL)
The serialization result for my custom Model class
class Model: NSObject, NSCoding {
var text: String
var amount: Double
// further code is omitted for readability
}
is the following in XML:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>$archiver</key>
<string>NSKeyedArchiver</string>
<key>$objects</key>
<array>
<string>$null</string>
<dict>
<key>$class</key>
<dict>
<key>CF$UID</key>
<integer>3</integer>
</dict>
<key>amount</key>
<real>22.219999999999999</real>
<key>text</key>
<dict>
<key>CF$UID</key>
<integer>2</integer>
</dict>
</dict>
<string>Test</string>
<dict>
<key>$classes</key>
<array>
<string>NSSecureCodingExample.Model</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>NSSecureCodingExample.Model</string>
</dict>
</array>
<key>$top</key>
<dict>
<key>$0</key>
<dict>
<key>CF$UID</key>
<integer>1</integer>
</dict>
</dict>
<key>$version</key>
<integer>100000</integer>
</dict>
</plist>
As an attacker I can manipulate the information in this document in such a way that not Model gets loaded during deserialization but another class.
For example I can change NSSecureCodingExample.Model to NSSecureCodingExample.FakeModel which corresponds to a class in my app
import Foundation
// For Testing Purposes only
class FakeModel: NSObject, NSCoding {
let text: String
let amount: Double
internal init(text: String, amount: Double) {
self.text = text
self.amount = amount
}
// ...
required init?(coder: NSCoder) {
/**
If serialized object in XML format was modified to use FakeModel
<dict>
<key>$classes</key>
<array>
<string>NSSecureCodingExample.FakeModel</string>
<string>NSObject</string>
</array>
<key>$classname</key>
<string>NSSecureCodingExample.FakeModel</string>
</dict>
Then app would crash :--!!!
*/
assert(1 == 2)
}
}
I can unarchive the object with NSKeyedUnarchiver later.
let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data) // This initializer enables requiresSecureCoding by default
unarchiver.requiresSecureCoding = false
let decodedDataObject = try unarchiver.decodeTopLevelObject()
unarchiver.finishDecoding()
// then cast decodedDataObject to your expected structured type
The app will crash because FakeModel gets loaded during the decodeTopLevelObject execution and it the FakeModel initializer contains malicious code (an assertion).
Apple introduced the NSSecureCoding protocol and multiple API enhancements for NSKeyedArchiver and NSKeyedUnarchiver. Once you adopt those then the compiler will be able to perform gated class checks so that an incorrect initialization cannot occur.
You can find the complete code, incl. a SwiftUI test application, on GitHub.

I made an interesting finding as it appears that it is not possible to unarchive an object stored in XML format which was archived with Secure Coding. If I am mistaken then I'd love to hear from you :)



