PHP之常见代码编写漏洞_数组

     PHP是弱类型语言,所以内置的很多函数,在进行转换和比较的时候,会有各种漏洞需要格外关注。不然很容易在安全上造成各种各样的漏洞。

PHP弱类型漏洞及其利用

md5加密漏洞(CTF中常见)

PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。php程序把每一个以“0x”开头的哈希值都解释为科学计数法0的多少次方,恒为0。

所以如果两个不同的密码经过哈希以后,其哈希值都是以“0e”开头的,那么php将会认为他们相同。

另外md5加密是有几率两个字符串不同,但是加密后的值是相同的情况,这种情况称为哈希碰撞。

<?php
$str1 = 's878926199a';
$str2 = 's214587387a';
 
echo json_encode([
 'md5_str1' => md5($str1),
 'md5_str2' => md5($str2),
 'bool' => md5($str1) == md5($str2)
]);
{
 md5_str1: "0e545993274517709034328855841020",
 md5_str2: "0e848240448830537924465865611904",
 bool: true
}

结果两个值加密后竟然相等(bool: true)。

对于 md5(str)==0 有很多绕过方法;总结一下:

  1. 开头为0或0e绕过
  2. 数组
  3. 只要经md5函数处理后开头没有数字都都可以,比如"c9f0f895fb98ab9159f51fd0297e236d"==0成立!!!,用到了字符串转换成整数!!!,如果开头没有数字的话,就只能转换成0了!!

0e开头的md5和原值:

s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s1885207154a
0e509367213418206700842008763514
s1502113478a
0e861580163291561247404381396064
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s155964671a
0e342768416822451524974117254469
s1184209335a
0e072485820392773389523109082030
s1665632922a
0e731198061491163073197128363787
s1502113478a
0e861580163291561247404381396064
s1836677006a
0e481036490867661113260034900752
s1091221200a
0e940624217856561557816327384675
s155964671a
0e342768416822451524974117254469
s1502113478a
0e861580163291561247404381396064
s155964671a
0e342768416822451524974117254469
s1665632922a
0e731198061491163073197128363787
s155964671a
0e342768416822451524974117254469
s1091221200a
0e940624217856561557816327384675
s1836677006a
0e481036490867661113260034900752
s1885207154a
0e509367213418206700842008763514
s532378020a
0e220463095855511507588041205815
s878926199a
0e545993274517709034328855841020
s1091221200a
0e940624217856561557816327384675
s214587387a
0e848240448830537924465865611904
s1502113478a
0e861580163291561247404381396064
s1091221200a
0e940624217856561557816327384675
s1665632922a
0e731198061491163073197128363787
s1885207154a
0e509367213418206700842008763514
s1836677006a
0e481036490867661113260034900752
s1665632922a
0e731198061491163073197128363787
s878926199a
0e545993274517709034328855841020

is_numeric漏洞

函数介绍:

is_numeric — 检测变量是否为数字或数字字符串。

函数原型:

bool is_numeric ( mixed $var ) 如果 var 是数字或数字字符串则返回 TRUE,否则返回 FALSE。

但是函数的范围比较广泛,不仅仅是十进制的数字。当碰到16进制数的时候,也会判断成数字。如果只是用is_numeric函数验证变量是否为数字,就很有可能插入一些特殊字符到数据库中。

容易引发sql注入操作,暴露敏感信息

echo json_encode([
 is_numeric(233333),
 is_numeric('233333'),
 is_numeric(0x233333),
 is_numeric('0x233333'),
 is_numeric('233333abc'),
]);

结果如下:

16进制数0x61646D696EASII码对应的值是admin。

如果我们执行了后面这条命令的话:SELECT * FROM tp_user where username=0x61646D696E,结果不言而喻

[
 true,
 true,
 true,
 false,
 false
]

in_array漏洞

n_array中是先将类型转为整形,再进行判断。

PHP作为弱类型语言,类型转换的时候,会有很大漏洞。

弱类型比较tips

弱类型比较时,如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换成数值并且比较按照数值来进行。 "0e"开头的字符串参与弱类型比较时,会将0e这类字符串识别为科学技术法的数字,0的无论多少次方都是零,所以会将它转换成0。 纯字符串与数值进行弱类型比较时,会先将字符串强制转换成数值,也就是0。

转换的时候,如果将字符串转换为整形,从字符串非整形的地方截止转换,如果无法转换,将会返回0。

<?php
var_dump(in_array("2%20and%20%", [0,2,3]));

结果如下:

bool(true)

in_array(search, array,[type])

函数用于搜索数组array中是否存在search的值。存在返回true,否则返回false。

type可选,type=true时表示检查搜索的数据与数组的值的类型是否相同(search为字符串时,区分大小写)。

漏洞例子:

$array=[0,1,2,'3'];
   var_dump(in_array('a', $array));		//bool(true)
   var_dump(in_array('1a', $array));		//bool(true)
   var_dump(in_array('1', $array, true));	//bool(false)

可以理解为,当type == false时,使用==进行比较;当type == true时,使用===进行比较。

switch()函数

switch()语句用于根据多个不同的条件执行不同的动作。

switch中是先将类型转为整形,再进行判断。

转换的时候,如果将字符串转换为整形,从字符串非整形的地方截止转换,如果无法转换,将会返回0。

$n = '2a';
   switch($n){
   	case 2:
   		echo 'yes';
   		break;
   	default:
   		echo 'no';
   		break;
   }
  
   //yes

此例中,为数值类型的case,switch会将参数转换为数值(此处可理解为通过==进行比较。)。

<?php
$i ="abc";
switch ($i) {
 case 0:
 case 1:
 case 2:
 echo "i是比3小的数";
 break;
 case 3:
 echo "i等于3";
}

结果如下:

i是比3小的数。

intval强转漏洞

PHP作为弱类型语言,类型转换的时候,会有很大漏洞。

转换的时候,如果将字符串转换为整形,从字符串非整形的地方截止转换,如果无法转换,将会返回0。

<?php
var_dump(intval('2')); //2
var_dump(intval('3abcd')); //3
var_dump(intval('abcd')); //0

数组比较

  • 在php手册中写道,当数组(array)与任何非数组进行比较时,数组总是最大的,所以有以下结果:
var_dump([]>0);		//bool(true)
   var_dump([]>9999);	//bool(true)
   var_dump([[]]>[]);	//bool(true)  #自然二位数组大于一维数组

bool类型的true比较

  • bool类型的true跟任意字符可以弱类型相等
var_dump(true == 'a');		//bool(true)

==与===的区别

在进行比较时,向将两边的类型转化成相同类型再进行比较,如果涉及到数值内容的字符串,则字符串会被转化成数值,并且按照数值大小进行比较(>、<同理)。

补充:

“根据php手册中所讲,字符串的开头决定了它转换后的值,如果该字符串以合法的数值开始,则使用该合法数值,否则其值为0”

如果字符创中没有包含.、e、E并且其数值在整形范围内,该字符被当做int来取值,其他所有情况都被作为float来取值。

0e开头的字符串被认为是科学技术计数法,且0e开头的数值为0

所以有如下结果:

var_dump(1 == '1');		//bool(true)
var_dump(1 == '1a');	//bool(true)
var_dump(0 == '0exxx');	//bool(true)
var_dump(0 == 'a1');	//bool(true)
var_dump(1 + '1');		//int(2)
var_dump(1 + '1a');		//int(2)
var_dump(1 + '0exxx');	//int(1)
var_dump(1 + 'a1');		//int(1)
===
先判断两边类型是否相同,再比较大小。

=是防止了弱类型比较漏洞,若两边类型不同,则直接false而不会再进行比较。

var_dump(1 === '1');	//bool(false)
var_dump(1 === '1a');	//bool(false)
var_dump(0 === '0exx');	//bool(false)
var_dump(0 === 'a1');	//bool(false)

 变量覆盖漏洞

通常将可以用自定义的参数值代替原有变量值的情况称为变量覆盖漏洞,经常导致变量覆盖漏洞的情况有:$$使用不当,extract()函数使用不当,parse_str()函数使用不当,import_request_variables()使用不当,开启了全局变量注册等

$$的使用

  1. $$为可变变量,简单应用如下:
$a = 'hello';
  $hello = 'world';
  echo $$a;			//world
  1. ctf实例:
<?php
  include('flag.php);	
  $_403 = "Access Denied";
  $_200 = "Welcome Admin";
  if ($_SERVER["REQUEST_METHOD"] != "POST")
      die("BugsBunnyCTF is here :p…");
  if ( !isset($_POST["flag"]) )
  	die($_403);
  foreach ($_GET as $key => $value)
  	$$key = $$value;
  foreach ($_POST as $key => $value)
  	$$key = $value;
  if ( $_POST["flag"] !== $flag )
  	die($_403);
  echo "This is your flag : ". $flag . "\n";
  die($_200);
  ?>

题目分析
此题关键在于,post提交的数据必须要有flag变量,然而这会覆盖最初的flag。**在于die($_200);,这会输出变量_200的值,所以可以把变量flag赋值给变量_200

解题方法
构造如下payload:

GET: /?_200=flag
  POST: flag=a

extract()函数

  1. 函数介绍
    extract()函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中每个元素,将在当前符号表中创建一个对应的变量
    语法:extract(array, extract_rules, prefix),详情参考PHP extract() 函数图【extract】
    栗子:
$a = "Original";
  $my_array = array("a" => "Cat", "b" => "Dog", "c" => "Horse");
  extract($my_array, EXTR_PREFIX_SAME, "dup");
  echo "\$a = $a; \$b = $b; \$c = $c; \$dup_a = $dup_a";
  
  //$a = Original; $b = Dog; $c = Horse; $dup_a = Cat
  1. ctf实例
<?php
  $flag = 'xxx';
  extract($_GET);
  if (isset($gift)) {
  	$content = trim(file_get_contents($flag));
  	if ($gift == $content) {
  		echo 'flag{This_is_flag}';
  	} else {
  		echo 'Oh..no..flag';
  	}
  } 
  ?>

题目分析
使用extract($_GET)接收GET请求的数据,并将键名和键值转变为变量名和变量值,然后通过两个if条件判断即可输出flag。可以使用GET提交参数和值,利用extract()对变量进行覆盖,从而满足if判断条件。

解题方法

GET:?flag=&gift=

extract()会将����和flag和gift的值覆盖为空。$content = file_get_contens()的文件为空或不存在时则返回空值(会出现警告),即可以满足条件$gift == $content

parse_str()函数

  1. 函数介绍
    parse_str()函数把查询字符串解析到变量中,如果未设置array参数,由该函数设置的变量将覆盖已存在的同名变量。
    语法:parse_str(string, array),详情参考PHP parse_str() 函数图[parsestr]
    栗子:
parse_str("name=Peter&age=43",$myArray);
  print_r($myArray);
  
  //Array ( [name] => Peter [age] => 43 )
  1. ctf实例
<?php
  error_reporting(0);
  if (empty($_GET['id'])) {
  	show_source(__FILE__);
  	die();
  } else {
  	include ('flag.php');
  	$a = "www.OPENCTF.com";
  	$id = $_GET['id'];
  	@parse_str($id);
  	if ($a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')) {
  		echo $flag;
  	} else {
  	exit('其实很简单其实并不难!');
  	}
  }
  ?>

题目分析
首先要求使用GET提交id参数,然后parse_str($id)对id参数进行数据处理,如果$a[0] != 'QNKCDZO' && md5($a[0]) == md5('QNKCDZO')则可以输出flag(此处可参考上文md5()函数漏洞),所以可通过覆盖变量a使之满足if条件。

解题方法

GET:?id=a[]=s1885207154a

通过parse_str($id)函数变量a[]=s1885207154a会覆盖$a = "www.OPENCTF.com"。然后md5(a[])=0e509367213418206700842008763514,md5('QNKCDZO')=0e830400451993494058024219903391,if条件即可满足。