php弱类型初级入门介绍

时间:2022-05-06
本文章向大家介绍php弱类型初级入门介绍,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

0x00 前言

最近DeDeCMS爆出来一个前台任意用户密码重置漏洞,由于前台resetpassword.php中对接受的safequestion参数类型比较不够严格,遭受弱类型攻击。借此,总结一下php弱类型。

0x01 知识介绍

php中有两种比较的符号 == 与 ===

<?php$a == $b ;$a === $b ;?>

=== 在进行比较的时候,会先判断两种字符串的类型是否相等,再比较。

== 在进行比较的时候,会先将字符串类型转化成相同,再比较。

如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换成数值并且比较按照数值来进行。

这里明确了说如果一个数值和字符串进行比较的时候,会将字符串转换成数值。

<?phpvar_dump("admin"==0); //truevar_dump("1admin"==1); //truevar_dump("admin1"==1) //falsevar_dump("admin1"==0) //truevar_dump("0e123456"=="0e4456789"); //true?> //上述代码可自行测试

1. 观察上述代码,"admin"==0 比较的时候,会将admin转化成数值,强制转化,由于admin是字符串,转化的结果是0自然和0相等。

2. "1admin"==1 比较的时候会将1admin转化成数值,结果为1,而“admin1“==1 却等于错误,也就是"admin1"被转化成了0,为什么呢??

3. "0e123456"=="0e456789"相互比较的时候,会将0e这类字符串识别为科学技术法的数字,0的无论多少次方都是零,所以相等。

php手册里面写道:

当一个字符串当作一个数值来取值,其结果和类型如下:如果该字符串没有包含'.','e','E'并且其数值值在整形的范围之内,该字符串被当作int来取值,其他所有情况下都被作为float来取值,该字符串的开始部分决定了它的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为0

<?php$test=1 + "10.5"; // $test=11.5(float)$test=1+"-1.3e3"; //$test=-1299(float)$test=1+"bob-1.3e3";//$test=1(int)$test=1+"2admin";//$test=3(int)$test=1+"admin2";//$test=1(int)?>

所以就解释了"admin1"==1 =>False 的原因

0x02 CTF/实战

md5绕过(Hash比较缺陷)

<?phpif (isset($_GET['Username']) && isset($_GET['password'])) { $logined = true; $Username = $_GET['Username']; $password = $_GET['password']; if (!ctype_alpha($Username)) {$logined = false;} if (!is_numeric($password) ) {$logined = false;} if (md5($Username) != md5($password)) {$logined = false;} if ($logined){ echo "successful"; }else{ echo "login failed!"; } }?>

题目大意是要输入一个字符串和数字类型,并且他们的md5值相等,就可以成功执行下一步语句。

上文提到过,0e在比较的时候会将其视作为科学计数法,所以无论0e后面是什么,0的多少次方还是0。md5('240610708') == md5('QNKCDZO')成功绕过!

QNKCDZO0e830400451993494058024219903391s878926199a0e545993274517709034328855841020s155964671a0e342768416822451524974117254469s214587387a0e848240448830537924465865611904s214587387a0e848240448830537924465865611904s878926199a0e545993274517709034328855841020s1091221200a0e940624217856561557816327384675s1885207154a0e509367213418206700842008763514

json绕过

<?phpif (isset($_POST['message'])) { $message = json_decode($_POST['message']); $key ="*********"; if ($message->key == $key) { echo "flag"; } else { echo "fail"; } } else{ echo "~~~~"; }?>

输入一个json类型的字符串,json_decode函数解密成一个数组,判断数组中key的值是否等于 $key的值,但是$key的值我们不知道,但是可以利用0=="admin"这种形式绕过。

最终payload message={"key":0}

array_search is_array绕过

<?phpif(!is_array($_GET['test'])){exit();}$test=$_GET['test'];for($i=0;$i<count($test);$i++){ if($test[$i]==="admin"){ echo "error"; exit(); } $test[$i]=intval($test[$i]);}if(array_search("admin",$test)===0){ echo "flag";}else{ echo "false";}

上面是自己写的一个,先判断传入的是不是数组,然后循环遍历数组中的每个值,并且数组中的每个值不能和admin相等,并且将每个值转化为int类型,再判断传入的数组是否有admin,有则返回flag。

payload test[]=0可以绕过。

下面是官方手册对array_search的介绍

mixed array_search ( mixed $needle , array $haystack [, bool $strict = false ] ) $needle,$haystack必需,$strict可选。函数判断$haystack中的值是存在$needle,存在则返回该值的键值。第三个参数默认为false,如果设置为true则会进行严格过滤。

<?php$a = array(0,1);var_dump(array_search("admin",$a)); // int(0) => 返回键值0var_dump(array_search("1admin",$a)); // int(1) ==>返回键值1?>

array_search函数 类似于== 也就是$a=="admin" ,当然是$a=0,当然如果第三个参数为true则就不能绕过。

strcmp漏洞绕过 php -v < 5.3

<?php $password="***************" if(isset($_POST['password'])){ if (strcmp($_POST['password'], $password) == 0) { echo "Right!!!login success";n exit(); } else { echo "Wrong password.."; }?>

strcmp是比较两个字符串,如果str1< str2 则返回< 0,如果str1大于str2返回>0, 如果两者相等返回0。

我们是不知道$password的值的,题目要求strcmp判断的接受的值和$password必需相等,strcmp传入的期望类型是字符串类型,如果传入的是个数组会怎么样呢?

我们传入 password[]=xxx 可以绕过,是因为函数接受到了不符合的类型,将发生错误,但是还是判断其相等。

payload: password[]=xxx

switch绕过

<?php$a="4admin";switch ($a) { case 1: echo "fail1"; break; case 2: echo "fail2"; break; case 3: echo "fail3"; break; case 4: echo "sucess"; //结果输出success; break; default: echo "failall"; break;}?>

这种原理和前面的类似,就不详细解释了

DeDeCMS任意用户密码重置

利用条件

  • 管理员开启了会员功能
  • 该会员没有设置安全问题

由于此漏洞是发送了一个密码重置的链接,访问此链接可以导致重置密码,我们由逆向思维来分析漏洞是怎么产生的。

发送链接的关键代码在 member/inc/inc_pwd_functions.php 73行

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."rnReply-To: $cfg_adminemail"; $mailbody = "亲爱的".$userid.":rn您好!感谢您使用".$cfg_webname."网。rn".$cfg_webname."应您的要求,重新设置密码:(注:如果您没有提出申请,请检查您的信息是否泄漏。)rn本次临时登陆密码为:".$randval." 请于三天内登陆下面网址确认修改。rn".$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&amp;id=".$mid."&amp;key=".$randval); } } else { return ShowMsg('对不起修改失败,请联系管理员', 'login.php'); } } elseif($type == 'UPDATE') { $key = md5($randval); $sql = "UPDATE `#@__pwd_tmp` SET `pwd` = '$key',mailtime = '$mailtime' WHERE `mid` ='$mid';"; if($db->ExecuteNoneQuery($sql)) { if($send == 'Y') { sendmail($mailto,$mailtitle,$mailbody,$headers); ShowMsg('EMAIL修改验证码已经发送到原来的邮箱请查收', 'login.php'); } elseif($send == 'N') { return ShowMsg('稍后跳转到修改页', $cfg_basehost.$cfg_memberurl."/resetpassword.php?dopost=getpasswd&amp;id=".$mid."&amp;key=".$randval); } } else { ShowMsg('对不起修改失败,请与管理员联系', 'login.php'); } }}

当$send == 'N' 时就会发送密码重置链接 此时调用newmail()的函数是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'); }}

漏洞触发的位置在 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(); }}

可以发现sn函数中的第四个参数,也就是所谓的$send为'N' 。

下面我们的目的清楚了,也就是需要让 if($row['safequestion'] == $safequestion && $row['safeanswer'] == $safeanswer)为真,执行sn函数,得到链接,重置密码。

在dede数据库中没有设置安全问题的用户safequestion=0 和safeanswer为空。

上面判断了当safequestion 和 safeanswer为空时将其赋值为 '', 提交safeanswer参数让其为空,所以第二个比较为真。我们现在的目的是让$row['safequestion'] == $safequestion为真,即我们传的参数要和0相等,所以safequestion肯定不能为空。

所以利用php弱类型比较:

<?phpvar_dump("0.0" == 0);//bool(true) safequestion为string类型?>

所以此时利用 safequestion=0.0&safeanswer= 可以使if为真,导致任意用户密码重置。