Exploits & Vulnerabilities
Analyzing an Old Bug and Discovering CVE-2021-30995
A vulnerability found in 2021 has been patched and re-patched in the months since it was reported. We analyse the bug and outline the process that led to the discovery of CVE-2021-30995.
On April 26, 2021 Apple patched CVE-2021-1740, which was a vulnerable function inside the system daemon process cfprefsd (these types of processes usually run in the background and handle system tasks). The bug could have been exploited to read arbitrary files, write arbitrary files, and get root privilege escalation. It was addressed in Apple’s Security Update 2021-002 (Catalina) for a variety of Apple operating systems, including iOS and macOS. However, in early August 2021, Zhipeng Huo, Yuebin Sun, and Chuanda Ding (all from XuanwuLab) presented an exploitation demonstration for the vulnerability during the DEF CON 29 security conference. Their presentation was called “Caught you - reveal and exploit IPC logic bugs inside Apple”.
While studying the slides, I found that the patch for CVE-2021-1740 was still vulnerable to arbitrary file read exploits. Apple fixed this flaw, and on September 20, 2021 assigned CVE-2021-30855 to the second patch.
However, I found that the second patch was still vulnerable to arbitrary file write and root privilege escalation. This vulnerability issue was brought forward and addressed on December 13, 2021, with Apple assigning CVE-2021-30995 as the third patch (credited to this author). Apple released Security Update 2021-008 (Catalina) to secure their affected products, so any users who installed these updates should be protected.
The report detailed below shows the investigation of the original vulnerability, and the process that led me to discover CVE-2021-30995.
Tracking the patching history
To fully investigate the vulnerability first reported in April, we should illustrate how an attack could work. The key logic of the vulnerable function [CFPDSource cloneAndOpenPropertyListWithoutDrainingPendingChangesOrValidatingPlist] is:
If the controllable plist file size is larger than 1MB, then it will be cloned to a temporary file with a random name and return the file descriptor of the new cloned one.
The arbitrary file write attack from the XuanwuLab researchers’ DefCon slides show that it replaced the fixed file name of the dst_path with a symbolic link before the API call clonefile.
After getting the primitive of arbitrary file write, there are some known ways to get root privilege escalation. One simple method involves the use of periodic scripts, outlined by Csaba Fitzl.
For the issues with the second patch, we can see the new API call fclonefileat at line 25 (in Figure 1). The target directory fd is -2, and v6 is the full path of the temporary plist file. So, I found that I could replace the target parent directory with a symbolic link to an arbitrary directory.
Discovering the exploit for the re-patched flaw
During my investigation into exploits for the vulnerability, I removed the target parent directory to create a symbolic link. However, I found the API call fclonefileat returned with an error (source plist file not found), which I deleted because it is in the same directory as the target temporary file.
I noticed that if the source plist file path is different from the v1->_actualPath, then the attack should work.
The next step was checking how the v1->_actualPath was generated. Through debugging, I found it was generated inside the function -[CFPDSource cacheFileInfoForWriting:euid:egid:didCreate:]:
We can see that self->_actualPath is fetched by API call fcntl with the fd_plist.
Fd_plist is opened at line 97 without flag 0x100 (O_NOFOLLOW), so I can replace the plist file with a symbolic link to another location. This will ensure the _actualPath is not in the same directory as the source plist file.
After faking the _actualPath, I removed the symbolic link and put a root shell script to be cloned instead. Then, replaced the parent directory of the _actualPath with a symbolic link to a root-owned location /usr/local/etc/periodic/daily.
Finally, the API call fclonefileat will help clone the root shell script to the root-owned directory. And, if we wait, we will get root privilege.
Here is a flow chart for this process:
Since this is a TOCTOU (Time of Check, Time of Use) issue, I did use some tricks to make my exploitation stable and successful. For example, I increased the priority of my attacking thread, retried many times in a loop, and so on.
The third patch for the re-patched flaw
Finally, Apple patched the issue by renaming the vulnerable function from cloneAndOpenPropertyListWithoutDrainingPendingChangesOrValidatingPlist to openPropertyListWithoutDrainingPendingChangesOrValidatingPlistAndReturnFileUID.
Of course, in the new function, it removes the API call fclonefileat, and it gives up the feature of backing up the big plist file to a temporary location.
Security recommendations
Apple has already issued multiple patches addressing this vulnerability, releasing new security updates as investigations into exploits continued. Security updates to both macOS 12.1 and iOS 15.2 addressed the vulnerability at the same time, and, as previously stated, users who installed these updates should be protected. However, failing to update or patch the said operating systems could allow a local attacker to exploit the vulnerability to elevate their privileges to root. It is highly recommended to install all updates provided by the manufacturers and software companies and keep your macOS/iOS system up to date.
Regularly updating systems and applications with the latest patches plays a critical role in mitigating the risks for end-users, ensuring that these security gaps can't be abused for malicious activities.