作者:锦行科技
类型转换是无法避免的问题。例如需要将GET或者是POST的参数转换为int类型,或者是两个变量不匹配的时候,PHP会自动地进行变量转换。但是PHP是一个弱类型的语言,导致在进行类型转换的时候会存在很多意想不到的问题。
当php进行一些数学计算的时候
<?php
var_dump(0 == '0'); // true
var_dump(0 == 'abc'); // true
var_dump(0 === 'abc'); // false
var_dump(1 == '1abc'); // true
var_dump('1e0'=='1e2'); // false
var_dump('0.0'==0); // true
var_dump('0.0'==''); //false
//还有下面这样的
if(md5('s878926199a')==0){
echo 'true';
}
?>
因为md5('s878926199a')=0e545993274517709034328855841020就是0的n次方,所以还是等于0
但是要注意:
"0e123456abc"=="0e1dddada"//false
这种返回的是为假
<?php
if (isset($_GET['which']))
{
$which = $_GET['which'];
switch ($which)
{
case 0:
case 1:
case 2:
require_once $which.'.php';
break;
}
}
?>
<?php
if(strcmp('1a',1)){
echo 'test';
}
?>
<?php
var_dump(in_array("1a",
array(1,2,3)));
?>
In_array函数和array_search函数的问题可以在in_array函数后面加一个true选项,就能解决比如:
<?php
if(in_array("1a", array(1,2,3),true)){
echo 'true';
}
?>
md5()
$array1[] = array(
"foo" => "bar",
"bar" => "foo",
);
$array2 = array("foo", "bar",
"hello", "world");
var_dump(md5($array1)==
md5($array2));
//回显为true
还存在一种十六进制余字符串进行比较运算时的问题。例子如下:
"0x1e240"=="123456"//true
"0x1e240"==123456//true
"0x1e240"=="1e240"//false
当其中的一个字符串是0x开头的时候,PHP会将此字符串解析成为十进制然后再进行比较,0×1240解析成为十进制就是123456,所以与int类型和string类型的123456比较都是相等。
下面我们来看一个dedecms的弱类型安全问题。
dedecms/member/resetpassword.php
//75行
else if($dopost == "safequestion")
{
$mid = preg_replace("#[^0-9]#", "", $id);
$sql = "SELECT safequestion,safeanswer,userid,email FROM #@__member WHERE mid = '$mid'";
$row = $db->GetOne($sql);
if(empty($safequestion)) $safequestion = '';
if(empty($safeanswer)) $safeanswer = '';
if($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer)
{
sn($mid, $row['userid'], $row['email'], 'N');
exit();
}
else
{
ShowMsg("对不起,您的安全问题或答案回答错误","-1");
exit();
}
}
管理员帐号admin的$row['safequestion']
默认是为’0’
(字符串),所以$safequestion
不能为空。否则不进入$row['safequestion'] == $safequestion
。而$_GET[‘safequestion ’]
传过来的值为字符串,当$_GET[‘safequestion ’
]为’0’
时进入if(empty($safequestion))
。当$_GET[‘safequestion ’]
为’0.0’
时不进入if(empty($safequestion))
,而’0’=’0.0’
进入if($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer)
,右边的$safeanswer
本身就为空。所以不用理。
跟进函数sn
:
function sn($mid,$userid,$mailto, $send = 'Y')
{
global $db;
$tptim= (60*10);
$dtime = time();
$sql = "SELECT * FROM #@__pwd_tmp WHERE mid = '$mid'";
$row = $db->GetOne($sql);
if(!is_array($row))
{
//发送新邮件;
newmail($mid,$userid,$mailto,'INSERT',$send);
}
//10分钟后可以再次发送新验证码;
elseif($dtime - $tptim > $row['mailtime'])
{
newmail($mid,$userid,$mailto,'UPDATE',$send);
}
//重新发送新的验证码确认邮件;
else
{
return ShowMsg('对不起,请10分钟后再重新申请', 'login.php');
}
}
这里从数据库取出来的值应该为空$sql = "SELECT * FROM #@__pwd_tmp WHERE mid = '$mid'";
于是进入
if(!is_array($row))
{
//发送新邮件;
newmail($mid,$userid,$mailto,'INSERT',$send);
}
注意一下$send
为N
我们跟进newmail
函数:
function newmail($mid, $userid, $mailto, $type, $send)
{
global $db,$cfg_adminemail,$cfg_webname,$cfg_basehost,$cfg_memberurl;
$mailtime = time();
$randval = random(8);
$mailtitle = $cfg_webname.":密码修改";
$mailto = $mailto;
$headers = "From: ".$cfg_adminemail."\r\nReply-To: $cfg_adminemail";
$mailbody = "亲爱的".$userid.":\r\n您好!感谢您使用".$cfg_webname."网。\r\n".$cfg_webname."应您的要求,重新设置密码:(注:如果您没有提出申请,请检查您的信息是否泄漏。)\r\n本次临时登陆密码为:".$randval." 请于三天内登陆下面网址确认修改。\r\n".$cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid;
if($type == 'INSERT')
{
$key = md5($randval);
$sql = "INSERT INTO `#@__pwd_tmp` (`mid` ,`membername` ,`pwd` ,`mailtime`)VALUES ('$mid', '$userid', '$key', '$mailtime');";
if($db->ExecuteNoneQuery($sql))
{
if($send == 'Y')
{
sendmail($mailto,$mailtitle,$mailbody,$headers);
return ShowMsg('EMAIL修改验证码已经发送到原来的邮箱请查收', 'login.php','','5000');
} else if ($send == 'N')
{
return ShowMsg('稍后跳转到修改页', $cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval);
}
}
else
{
return ShowMsg('对不起修改失败,请联系管理员', 'login.php');
}
}
这里直接是对dede_pwd_tmp
表插入临时密码,临时密码为$randval = random(8);
是8位,但是别急。紧接着插入完成之后ShowMsg('稍后跳转到修改页', $cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&id=".$mid."&key=".$randval);
也就是说在insert
完成之后跳转把$randval
输出到了页面。
也就是这个key,这个key就是管理员的临时密码。
利用方法:
先注册一个帐号并登录,然后访问:
http://localhost//member/resetpassword.php?dopost=safequestion&safequestion=0.0&safeanswer=&id=1
就可以发现上面那个包了
不要相信用户输入。应多使用===来避免弱类型安全问题.