写在开头:
这里包含了 ctfshow-web 入门 - PHP 特性里面所有题目的 wp,是我在刚接触 ctf 那段时间刷的,由于当时没什么基础,wp 也是复现大佬的写法,一边做一边学也是。最近搭博客也是想着整理一下以前写的东西就给搬过来了,写的不好大家多担待些,orz
# web89
得到 flag 的途径就是让 num 中包含数字,但又会被 preg_match
给过滤掉,可以用数组的形式使 preg_match
报错,达到绕过
# web90
采用了强比较,intval 中num==="4476"`
# web91
先是将/im 成功执行,使
/^php$/` 执行失败:
# web92
这是一个 num 和 4476 的弱比较,绕过只需将 4476 转换为 8 进制,由于有 intval 函数的存在,还需在数字前添加 0:
# web93
比上题多过滤了字母,是为了防止 e 的应用,因为 php 中 e 可以不表示科学计数法,从而以带字母数字的形式通过弱比较,而 intval 也会在 e 处停下,这里仍使用八进制解法即可
# web94
strpos 函数查到 num 中的第一个 0 出现的位置,也就是说,直接输入 010574 的话,strpos 会返回 0,而!0 的结果就是 1,所以 0 绝对不能出现在第一个位置,可以采用 4476.0
或 %20010574
或 %0a010574
# web95
过滤了 .
号, %20
和 %0a
还能照常绕过
# web96
在 php 中 ./
表示当前目录,既可以绕过弱比较,又可以正确输出 flag
也可以使用 php://filter伪协议
,通过 base64 编码获取 flag
# web97
post 两个变量使其内容不等又是 md5 值相等,很显然是构造数组,md5 面对数组会返回 null 值
# web98
运用了 php 中的三元运算符和 & 地址引用,改换代码如下:
get 随便传参就行,改为 post
# web99
in_array
的第三个参数没有被设置,说明 n 和 allow 数组中的值会进行弱比较,根据 file_put_content
函数,写入 php 文件,由 content 写入 php 代码:
查看目录:
# web100
v0 需要是大于 0 的数数字,由于运算符优先性,= 比 and 优先,所以 v1 大于 0 即可,v2 中不能含有;而 v3 必须含有;
拼接起来的 $v2ctfshow$v3
可以将中间的字符注释掉,这样只需要由 v2 输出 $ctfshow 就行,使用 var_dump
或者 print_r
输出0x2d
用 -
代替就能得到 flag 字符串了
# web101
题目中给出了提示,说 flag 在 ctfshow 的类中
了解到可以用 reflectionclass 建立反射类来获取数据元的信息
# web102
其中 s 是删除了 v2 的前两个得到的,将作为 v1 的参数来获得 str 的表达式,其中,v2 应为一句话木马,最后使用 v3 将其作为文件传上去
其中,v2 必须是数字或者带 e 的数字,通过将代码 base64 编码,转 ascii 码再 16 进制转换,成功得预期数字字符串:
# web103
比上题多过滤了 str 变量中 php 的部分,不影响 v3,所以照常使用 php 伪协议,文件中的命令 <?php
一直是用 <?=
来绕过,因为带 php 构造不出预期的字符
# web104
与 md5 同理,shal 也无法处理数组
# web105
考察变量覆盖和 die 函数的特性,要绕过前面两个 die 函数,先覆盖 get:
使 suces 的地址指向 flag
再覆盖 post:
由于写的是 error=suces,不会通过强比较,但是 error 的地址又能指向 flag
这样,无论是剩下的哪个 die 都能输出 flag 的内容,由于 post 没有 flag=flag
所以先 die 了 error(die 是停止运行并输出内容):
# web106
和 web104 类似,多了 v1 和 v2 的比较,还是用数组即可:
# web107
parse_str
函数将解析 v1 中的变量,由于下面的 if 判断语句得出,应该使 v1=flag
;而后与 v3 的 md5 值比较,这里我们直接使 v3 的 md5 值为 null,这样 v2['flag']
也为 null 就能输出 flag
# web108
strrev 函数有着倒序的作用, 0x36d
是 16 进制,转换为 10 进制就是 877,由于倒序,传入为 778 即可,ereg 函数用来匹配字符,所以 c 中需要字母可以通过截断符 %00
来防止因为有数字返回 false,同样,弱比较可以通过数字末尾带字母的方式匹配成功
# web109
看到 new 想到建立反射类
# web110
学习了一手佬的 wp,了解到还有 FilesystemIterator
这种迭代器能够差目录,配合上 getcwd()
函数,找到文件
# web111
看到题目定义了一个 getFlag 的函数,按照 preg_match
的限制 v1 应该为 ctfshow, 但是通过函数的作用,v2 不能直接输入,因为会受到内部函数无法调用外部变量的影响,所以采用了 GLOBAL 常量数组,将其变为全局变量
# web112
题目先是通过 is_file
函数判断是否为一个文件,然后用 filter
函数来进行过滤,发现没有过滤 php,考虑使用 php 伪协议来读取文件
# web113
发现过滤了 filter,那么 php 伪协议就不能用了,了解到,可以重复多次输入 /prof/self/root
这个根目录表达方式来超过 is_file
函数的输入限制,让其返回 false,还可以通过 zlib 来绕过
# web114
把上题使用的 compress 和 root 都过滤了,但发现 filter 反而没有,直接使用 php://filter伪协议
:
# web115
首先,会过滤掉 0x
, 0
, .
, e
, +
这几个通过进制变换得到的东西
随后,通过 is_numeric
, trim
, filter
函数来判断,其中 trim()
函数会去掉 num 里的 %0a
%0b
%0d
%20
%09
,只有换页符 %0c
不会,其次,!== 是强比较,会比较数据类型,字符串肯定不会等于 36
# web123
可以看到,想要得到 flag 有两种方式,一种是通过 c,一种是通过 fl0g,但前面的代码已经用!isset 的方式将 fl0g 给否决掉了,只能 post fun,而且需要含有 CTF_SHOW
和 CTF_SHOW.COM
,由于 php 特性,导致 +
, .
, [
都会被转换成 _
,但 [
被转化之后,后面的符号会保持不变所以构造 payload:
# web125
过滤了一些用法,要找到不含上述符合的函数来读取,想到 highlight_file
绕过
# web126
这里没什么思路,参考了一下 wp,了解到 assert 这个断言函数,通过断言引用了_SERVER 方式,将$a变成了数组来传递$fl0g,成功绕过了
$_GET` 对于 fl0g 的限制,从而得到 flag
# web127
waf 函数获得 $url 中的脚本信息,利用 extracr
函数转化为 get 请求,由于过滤了 _
,所以用 php 特性,将 _
用空格来代替,构造 payload:
# web128
看到 var_dump
的参数需要经过两次函数回调的变换,而且 f1 还不能含有字母数字,看了 wp,还真是骚操作,用 _()
来代替 gettext()
函数,其作用是原封不动输出参数,符合我们的要求,还加上 get_defined_vars()
函数,用于返回所有已定义变量构成的数组:
# web129
stripos
检索 ctfshow 的位置,需要大于 0,还要考虑 readfile
函数,采用目录穿越的操作构造 /../
来穿越目录:
# web130
preg_match
针对 ctfshow 字符串的前面做出了过滤,却没有关注后面,直接输入 ctfshow 或者后面加字符都可以绕过
# web131
在 ctfshow 前面多了 36D,运用正则表达溢出绕过 preg_match
函数,根据题目给出的提示,写了 25 万次 very
# web132
打开题目发现是个网页,网页源码没发现什么有价值的东西,通过 dirsearch 去扫描后台目录
抓到了 admin 和 asserts,进入 admin 页面,发现源码
三个变量,其中,由于 &&
的优先级高于 ||
,所以只要满足 $username==="admin"
和 $code=='admin'
就可以拿到 flag,password 随便填写
# web133
在六个字符的限制下,写 flag.php
都不够用,在 wp 启发下,了解到构造F=`$F`;
+ 其他命令的形式,由于字数限制, $F
肯定是会被读入的,但由于之前已经给 F 赋了值,所以被读入部分也可以视为 $F`;+其他命令
,所有,会有其他命令被输入进去,通过 curl 外带,筛选 flag 带出
# web134
@parse_str($_SERVER['QUERY_STRING']);
查询 url 中 ?
后面的键值对,并且解析字符串,将解析后的结果设为 PHP 的超级全局数组,再通过 extract($_POST);
将数组中的键作为 POST 的变量名,数组中的值作为 POST 的值,通过传入 _POST['key1']=36d
来绕过对于 GET 和 POST 的检查
# web135
先前的 curl 外带失效了,还是自身引用自身,采用 cp 复制 flag.php 内容到 1.txt 文件中
访问 1.txt
# web136
用常规命令执行即可,但发现没有回显,运用 linux 的 tee 命令,将 ls 的内容写在文件 2 中
发现 flag 文件 f149_15_h3r3
,重复步骤抓取文件,拿到 flag
# web137
涉及类的静态调用,直接用就行
# web138
这题冒号的输入被限制了,使用数组的方式进行绕过
# web139
和 web135 一样的题干,但是禁了 tee 命令,搬运了两个脚本来获取文件名和内容
# web140
看到最后一个 if 处是一个数字和字符串的弱比较,想要得到 True,intval 的返回值应该为 0
其中 f1 和 f2 都得是由字母数字构成的,而我们需要的 $f1($f2())
的返回值应该为字母开头的字符串,空数组,0 或者是 False,所以可以通过构造上述几种结果,这里我选择了构造 FALSE
通过 system 函数的报错来拿到 flag
# web141
其中,v1 和 v2 随便用数字填,v3 根据 preg_match
来看可以是字母数字和下划线,在 eval 函数中绕过 return 可以添加运算符,而添加的运算符不会影响函数执行。由于 v3 的限制,可以通过取反来绕过
# web142
主要解决 sleep 的问题,直接令 v1=0 即可,这样 d=0,查看源码拿到 flag
# web143
和 web141 类似,v1 和 v2 是数字就行,v3 被过滤了字母数字,取反号和或都被过滤了,考虑用异或绕过, ;
用 ?>
代替,运算符中 + 和 - 也被过滤,用 *
即可,构造 payload:
# web144
v3 被限制成了一个字符,v2 也被 preg_match
限定了范围,v1 还是随便取数字就行,这里转换一下,将 v3 变为运算符,v2 来执行命令,此处我采用了或运算
# web145
v1 和 v2 仍旧任意取数字,v3 使用取反来构造命令,了解到,return 还可以通过?构造判断的方式来绕过: eval("return 1?system('tac f*'):1")
# web146
过滤了冒号,上题的绕过不适用了,改为 || 运算符
# web147
这题的解法主要靠 create_fuction
这个函数,其主要思路就是:
# web148
发现没有对 ^
做过滤,考虑到异或绕过:
# web149
file_put_contents
能把字符串输入到文件中,能创建文件也能覆盖掉文件,不是 index.php 的文件都会被删除,将一句话木马直接写入 index.php 中
# web150
文件包含类型的题目,通过在 UA 处写一句话木马,然后包含 /var/log/nginx/access.log 就可以使用我们写的一句话了
# web150_plus
一样的题干,不过打了补丁,因为上题的解法为非预期解
构造 ?..CTFSHOW..=phpinfo
就可以看到 phpinfo 信息
原因是 ..CTFSHOW..
解析变量成 __CTFSHOW__
然后进行了变量覆盖,因为 CTFSHOW 是类就会使用 __autoload()
函数方法,去加载,因为等于 phpinfo 就会去加载 phpinfo