How to test object substitution attacks and NSSecureCoding
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
NSKeyedArchiver
andNSKeyedUnArchiver
either 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) {
assert(1 == 2)
}
}
I can unarchive the object with NSKeyedUnarchiver
later.
let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data) unarchiver.requiresSecureCoding = false let decodedDataObject = try unarchiver.decodeTopLevelObject() unarchiver.finishDecoding()
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 :)
Originally published at https://blog.eidinger.info.