写在开头:
这里包含了 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

