Show Notes

179 - Client-Side Path Traversal and Hiding Your Entitlement(s)

Some funny vulns in an undisclosed forum’s “teams” feature where users could create their own teams and request to join others as different roles. Users could request to join a team as any non-admin role, and a team admin could accept the request. The problem is, the requests were vulnerable to IDOR-type issues.

First bug: Sending Request to Join a Team as Admin The request to the team-join-post endpoint contains Team and TeamRole objects with subsequent ObjectIds for which team to join and which role they’re requesting respectively. A user can tamper with this request to swap the TeamRole object ID to that of the admin role. Submitting a request to join the team as an admin.

Second bug: Approving Team Joining Request on Behalf of Admin On the admin side, the team-join-response-post endpoint would be used to approve a request to join the team. The body of the request containing two object IDs, Team and TeamMember, and a boolean isApproved. There was no authorization checking on this endpoint however, the attacker, knowing the Team object ID and their own object ID for the TeamMember field could approve their own requests.

A post by project zero on a vuln in a new library used for DER entitlements. Entitlements are Apple’s fine-grained permission system and essentially define what capabilities an app or service has. Third party apps can request a limited subset of entitlements, which are signed off by Apple before making its way to the app store. Previously, entitlements were XML-based and subject to parsing bugs as it was quite complicated and there were 4-5 different parsers. In iOS 15, Distinguished Encoding Rule (DER) entitlements were introduced, which use the X.690 binary format of tag-length-data rules.

Vulnerability While this simplified parsing down from 4-5 parsers to 1, it still had a few different methods that would traverse the entitlements (recursivelyValidateEntitlements(), der_vm_iterate(), and der_vm_execute_nocopy()). The problem is that there were differences in how each of these algorithms processed the a malformed SEQUENCE inside of the entitlements blob. There are two length values that matter for this issue. First, in DER every entity being encoded has a length value representing the number of bytes the entity takes up. Then specifically for a SEQUENCE the value starts with the count of entities in the sequence. The vulnerability happens in a mismatch between how the parsers handle a sequence with sequence whose contents are smaller than the length in bytes provided earlier.

In the case of der_vm_iterate() it would jump ahead from where the sequence ended to where its Length value indicated it ended. Ignoring any of the extra content. der_vm_execute_nocopy however would continue processing from the end of the last entity in the sequence.

This bug effectively allows you to smuggle entitlements by embedding them as children of other entitlements. They’d be visible to der_vm_execute_nocopy() for querying the entitlements, but hidden from functions that use der_vm_iterate() like CEContextIsSubset(). Exploiting this involved getting around a few roadblocks in the DER structure (such as the fact they had to be alphabetical), and the entitlements they could use were limited as many userspace daemons use the legacy XML-based entitlement system. However, the kernel was a viable target, and they were able to invoke kext_request API methods for kernel extensions, which is a highly privileged functionality.

Patch This bug was fixed by accident when Apple refactored DER entitlement parsing to take the parent DERs as input. However, Apple also put out a patch that adds some additional validation to ensure a key/value sequence doesn’t contain other elements.

tl;dr Android Parcels have their own memory pool rather than being free’d all the way back to the general Java memory pool. This custom memory management, combined with a bug resulting in a dangling reference in a Parcel to an older version of the parcel creates a “use-after-free” like situation

Quick background, you’ve got bundles, bundles are just key-value mappings with a limited set of allowed value types. One of the allowed value types is a Parcelable. Parcels have been a source of vulnerabilities in Bundle processing for awhile. “Unbalanced” parcels are where the parcel implementation does not read the same amount of data as it had written, resulting in later values from the Parcel being read from the wrong offset.

LazyValue are attempting to deal with these problems by storing size information with each value in the parcel, allowing it to know how much data should be read, and introducing an extra layer of abstraction around the value. There is also some functionality for string deduplication using a Parcel.ReadWriteHelper. The details of how that works doesn’t matter but it changes the flow a bit. Adding in a step to immediately deserialize the parcel so the helper can be released. It copies the Parcel, and eventually tries to resolve all of the LazyValues inside.

Normally after this there would be no LazyValues, however if an exception is raised (atleast within teh system_server), the LazyValue is just ignored and left in-place. When this happens you have a LazyValue that has been copied into this new Parcel, but that still points to the value in the original Parcel.

Normally this wouldn’t be an issue, but Parcels have their own memory management system apart from the Java garbage collector. So that original Parcel may get recycled into that pool, and reused for another parcel. Creating a situation very similar to a use-after-free.