### 常见的弱类型问题 #### 类型转换问题 类型转换是无法避免的问题。例如需要将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(...
### 常见的弱类型问题 #### 类型转换问题 类型转换是无法避免的问题。例如需要将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 ``` 就可以发现上面那个包了 #### 结语 不要相信用户输入。应多使用===来避免弱类型安全问题.