آغازگر امنیتی Swift deserialization – انجمن DEV

Deserialization فرآیند تبدیل داده ها از یک فرمت سریالی، مانند JSON یا باینری، به شکل اصلی خود است. سوئیفت پروتکلهای متعددی را ارائه میکند که به کاربران اجازه میدهد اشیا و مقادیر را به و از فهرستهای دارایی، JSON و دیگر نمایشهای باینری مسطح تبدیل کنند.
سریالزدایی همچنین میتواند آسیبپذیریهای امنیتی نامشخصی را در پایگاه کد کاربر ایجاد کند که مهاجمان میتوانند از آنها سوء استفاده کنند. این وبلاگ به جزئیات آسیبپذیریهای deserialization در Swift میپردازد که ممکن است هنگام استفاده از APIهای محبوب رخ دهد. NScoding و NSSecureCodingو نحوه پیشگیری صحیح از آن
NSCoding
NSCoding پروتکلی است که اپل ارائه میکند و میتواند برای سریالسازی و سریالزدایی دادهها استفاده شود. پروتکل پیشفرض برای سریالسازی و سریالزدایی دادهها در NSCoding در برابر حملات جایگزینی شی آسیبپذیر است – که میتواند به مهاجم اجازه دهد تا از اجرای کد از راه دور استفاده کند.
کلاس زیر را انتخاب کنید که NSCoding را با دو روش پیاده سازی می کند – initWithCoder:
و encodeWithCoder:
. کلاسهای مطابق با NSCoding را میتوان سریالسازی کرد و به دادههایی تبدیل کرد که میتوانند در یک دیسک بایگانی شوند یا در سراسر شبکه توزیع شوند.
import Foundation
class Employee: NSObject, NSCoding {
var name: String
var role: String
init(name: String, role: String) {
self.name = name
self.role = role
}
required convenience init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: "name") as? String,
let role = aDecoder.decodeObject(forKey: "role") as? String else {
return nil
}
self.init(name: name, role: role)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(role, forKey: "role")
}
}
کلاس فوق با پروتکل NSCoding مطابقت دارد و دارای دو ویژگی نام و نقش است. این کلاس همچنین پیاده سازی می کند required init(coder:)
مقداردهی اولیه برای رمزگشایی مشخصات نام و نقش از یک آرشیو و encode(with:)
روشی برای رمزگذاری مشخصات نام و نقش در یک آرشیو.
سپس یک شیء کارمند را می توان با استفاده از NSKeyedArchiver ایجاد و در یک فایل بایگانی کرد.
// Archive the Employee object to a file
let person = Employee(name: "John", role: "consultant")
let fileURL = URL(fileURLWithPath: "/tmp/file")
NSKeyedArchiver.archiveRootObject(person, toFile: fileURL.path)
سپس میتوان با استفاده از NSKeyedUnarchiver برای چاپ مقادیر نام و ویژگیهای نقش، این مورد را از فایل به شیء کارمند خارج کرد.
// Unarchive the Employee object from the file
if let unarchivedPerson = NSKeyedUnarchiver.unarchiveObject(withFile: "/tmp/file") as? Employee {
print("Name: \(unarchivedPerson.name), Role: \(unarchivedPerson.role)")
} else {
print("Failed to unarchive Employee object")
}
این می تواند به طور بالقوه به روش زیر مورد سوء استفاده قرار گیرد. فرض کنید کلاس های دیگری از جمله کلاس زیر در پایگاه کد وجود دارد. این کلاس دارای یک ویژگی به نام “command” است – رشته ای که یک مسیر فایل را نشان می دهد. کلاس همچنین دارای دو روش “encode(with:)” و “init?(coder:)” است که برای مطابقت با پروتکل NSCoding لازم است.
ویژگی command از یک شیء کدگذاری شده گرفته شده و به داخل می رود sink1
تابع – که ویژگی را به عنوان بخشی از یک فرمان اجرا می کند.
import Foundation
class ExampleGadget: NSObject, NSCoding {
let command: String
internal init(command: String) {
self.command = command
}
func encode(with coder: NSCoder) {
coder.encode(command, forKey: "command")
}
required init?(coder: NSCoder) {
command = coder.decodeObject(forKey: "command") as! String
super.init()
var result = sink1(tainted: command)
print(result)
}
func sink1(tainted: String) -> String {
let process = Process()
process.executableURL = URL(fileURLWithPath: "/bin/bash")
process.arguments = ["-c", tainted]
let pipe = Pipe()
process.standardOutput = pipe
process.launch()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let output: String = String(data: data, encoding: .utf8) else { return "" }
return output
}
}
یک مهاجم می تواند از این کلاس برای اجرای کد و انجام یک حمله deserialization استفاده کند که می تواند منجر به تزریق دستور شود.
NSSecureCoding
NSSecureCoding یک جایگزین امن برای NSCoding است که ویژگی های امنیتی پیشرفته ای را برای محافظت در برابر حملات deserialization ارائه می دهد. برخلاف NSCoding، NSSecureCoding الزامات امنیتی سخت تری را بر اشیاء رمزگذاری شده و رمزگشایی شده تحمیل می کند، از جمله نیاز به یک سلسله مراتب کلاس تعریف شده و اقدامات امنیتی خاص برای پیاده سازی در خود کلاس ها. این اقدامات اضافه شده به جلوگیری از دستکاری داده های سریالی که می تواند منجر به ایجاد اشیایی با قابلیت اجرای کدهای مخرب شود، کمک می کند و در نتیجه امنیت کلی سیستم را افزایش می دهد. با این حال، بسته به نحوه استفاده از آن ممکن است حملات deserialization همچنان رخ دهد.
رمزگشایی بدون تأیید نوع کلاس
در مثال زیر، کد برای مطابقت با NSSecureCoding تغییر کرده است. این supportSecureCoding
خاصیت روی true تنظیم شده است و درست مانند قبل، the decodeObject
این روش برای جداسازی اشیاء رمزگذاری شده استفاده می شود.
import Foundation
class Employee: NSObject, NSSecureCoding {
public static var supportsSecureCoding = true
var name: String
var role: String
init(name: String, role: String) {
self.name = name
self.role = role
}
required convenience init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: "name") as? String,
let role = aDecoder.decodeObject(of:Employee.self, forKey: "role") as? String else {
return nil
}
self.init(name: name, role: role)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(role, forKey: "role")
}
}
با این حال، همانطور که در مستندات توسعه دهنده اپل برای NSSecureCoding بیان شده است، این تکنیک به طور بالقوه ناامن است، زیرا زمانی که نوع کلاس را تأیید می کنید، شی قبلا ساخته شده است – و اگر این بخشی از یک کلاس مجموعه باشد، به طور بالقوه در یک شی درج شده است. نمودار تنظیمات supportsSecureCoding
به true
یک شی را به عنوان امن برای رمزگشایی برچسب گذاری می کند. با این حال، هیچ تأییدی توسط NSSecureCoding برای تأیید نوع این شی و اینکه آیا این موضوع به کلاس کارمند مربوطه مربوط می شود، انجام نمی شود.
برای رفع این مشکل، of
کلید را می توان با مشخص کرد decodeObject(of:Employee.self, forKey: "name")
، که فقط اشیاء کلاس مشخص شده (در این مورد کارمند) را رمزگشایی می کند. این تضمین می کند که ویژگی نام فقط به عنوان یک رشته رمزگشایی می شود، نه به عنوان یک شیء ساخته شده به طور مخرب. متناوبا، decodeObjectOfClass
می توان از روش استفاده کرد – که یک شی را برای کلید محدود به کلاس مشخص رمزگشایی می کند. علاوه بر این، decodeObjectForKey
و decodeTopLevelObjectForKey
نباید از روش هایی استفاده کرد که این موضوع نیز تحت تاثیر این موضوع قرار گرفته و از نظر اپل منسوخ شده است.
supportsSecureCoding روی False تنظیم شده است
همانطور که در مستندات توسعه دهنده اپل بیان شده است، باید اطمینان حاصل کنید که گیرنده ویژگی این کلاس برمی گردد true
هنگام نوشتن کلاسی که از کدگذاری امن پشتیبانی می کند. تنظیمات supportsSecureCoding = false
همچنان با NSSecureCoding مطابقت دارد و به توسعه دهندگان احساس امنیت کاذب می دهد و در عین حال اجازه حملات deserialization را می دهد.
ایمنزدایی سوئیفت
به طور خلاصه، ملاحظات امنیتی باید در هنگام جداسازی داده های کاربر با استفاده از NSCoding و NSSecureCoding در نظر گرفته شود. در حالی که اسناد ممکن است به نظر برسد که NSSecureCoding به طور پیشفرض از سریالزدایی جلوگیری میکند، اینطور نیست و مواردی وجود دارند که در آن deserialization ناامن هنوز امکانپذیر است. هر زمان که از توابع رمزگشایی با NSSecureCoding استفاده میکنید، اطمینان حاصل کنید که نوع شیء که deserialized شده است تأیید شده است.
منابع
کد نمونه استفاده شده در این وبلاگ را می توانید در اینجا پیدا کنید: https://github.com/snoopysecurity/swift-deserialization-security-primer