• PHP代码审计13—弱类型


    一、PHP弱类型基础

    简介:

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

    1、比较操作符弱类型

    对于PHP来说,比较操作符有两种,=====

    • == : 在进行比较的时候,会先将字符串类型转化成相同,再比较
    • === : 在进行比较的时候,会先判断两种字符串的类型是否相等,再比较

    在使用“==”的时候, 如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换成数值并且比较按照数值来进行 这久导致了数值比较的弱类型问题。常见的情形如下:

    • hash比较缺陷

      示例:
      "0e861580163291561247404381396064" == "0e509367213418206700842008763514"   //true
      导致这种问题产生的原因在于,hash字符串以0开头,会认为这是一个数字,然后解析为0*10^n,最终两变得的结果都为0,所以判断结果为true
      • 1
      • 2
      • 3
    • 十六进制转换比较缺陷

      示例:
      "0x1e240"=="123456"     //true
      导致这个问题是原因也是由于php将0x1e240识别为数字,就会先将0x1e240准换为整数在进行比较。
      
      • 1
      • 2
      • 3
    • 类型转换

      
      $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)
      ?>
      PHP手册:当一个字符串被当作一个数值来取值,其结果和类型如下:
      		如果该字符串没有包含’.,’e’,E’ 并且其数值值在整形的范围之内该字符串被当作 int 来取值,其他所有情况下都被作为 float 来取值,该字符串的开始部分决定了它的值,如果该字符串以合法的数值开始,则使用该数值,否则其值为 0
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 布尔欺骗

      示例情况:
      $data ={user:true;pass:true}
      if ($data['user'] == 'admin' && $data['pass']=='secirity'){
          print_r('logined in as bool'."\n");
      }
      //运行结果:logined in as bool
      原理解释:
        当我们传入的user和pass=true时,会认为时采用的布尔类型的数值进行比较,就会将"admin""secruity"转换为布尔型进行比较,而在PHP中,只有0false0.0、不包含任何元素的数组、不包含任何变量的对象、null会被准换为0(false),其他的都会被准换位1(true)。所以最终的结果就是if(1=1 && 1=1),结果为true.
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    • 数字转换问题

      示例1$user_id = ($_POST['user_id']);
      if ($user_id == "1")
      {
          $user_id = (int)($user_id);
          #$user_id = intval($user_id);
          $qry = "SELECT * FROM `users` WHERE user_id='$user_id';";
      }
      $result = mysql_query($qry) or die('
      ' . mysql_error() . '
      '
      ); 在这里,当我们传入的数字ID=0.9999999999999999999999,在进行if判断的时候,会将ID转换为1继进行判断,而在查询的时候,数据库会进行向下取整,认为ID=0,从而查询ID=0的用户数据。 示例2:intval强制转换的弱类型问题 if (intval($qq) === '123456') { $db->query("select * from user where qq = $qq") } 如果采用上述方法,intval将转换所有数字直到遇到非数字为止,随意如果我们传入123456 union select version()inval($qq)的值将会是123456,但是在查询的时候,由于没有使用intval准换后的ID,就导致了注入。
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

    2、 存在弱类型问题的函数

    • MD5(),sha1()

      问题:这两个函数不能处理数组,对于传入的数组,返回结果为null,比如下面这种情况,就会导致null==null,结果为true.
      $str = array('123' =>123 );
      $test=array('234' =>234);
      if(md5($str)==md5($test)){
          echo 'success.......';
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • strcmp()

      这个函数的作用是比较两个字符串并且区分大小写
      当字符串1大于字符串2就返回>0,当字符串1小于字符串2就返回<0,相等则返回0
      示例:strcmp($str1,$str2)
      绕过方法:通过传入数组的方法,让该函数处理时发生错误,返回结果0,导致绕过。
      示例情况:
      <?php
      		$POST=$_POST[name];         //POSTdata: name[]=1
          $pass1="ASDSADSADAD";
          if(isset($_GET['pass'])){
              if(strcmp($_GET['pass'],$pass1)==0){
                  echo "success,233";
              }else{
                  echo " fail,322";
              }
          }
      ?>
      //result : success,233
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    • switch()

      问题在于switch选择的时候,处理的变量会被强转为int类型。
      <?php
        $a=$_GET['$a'];
        switch ($a) {
        case 1:
          echo "error";
          break;
        case 2:
          echo "success";
          break;
        }
      ?>
      //当我们传入2abcdef时,输出结果为:success
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • in_array()

      问题点:
      函数格式 in_array(需要在数组内搜索的数值,被搜索的数组,一个可选参数,设置TURE检查数据和数组值类型是否相同),这个函数的作用是检查数组中是否存在某个值,当没有最后的检测参数为true时,默认为松散比较,导致弱类型,传入不同数据类型来绕过。示例:
      <?php
      $array=[1,2,3];
      var_dump(in_array('aaa', $array));   //false
      var_dump(in_array('1aa', $array));   //true
      ?>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • array_search()

      该函数的作用是在数组中搜索某个键值,并返回键名
      函数格式 array_search(要搜索的键值,被搜索的数组,设置TURE检查数据和数组值类型是否相同)
      当数据类型不同会先把原有数据的数据类型的进行转换,导致弱类型的产生
      示例:
      <?php
      $array=[1,2];
      var_dump(array_search('aaa', $array));   //boolen false
      var_dump(array_search('2aa', $array));   //int 1
      ?>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 其他函数:intval() 、is_numeric、strpos等都存在类似问题。

    3、由弱类型引发的历史漏洞

    • DedeCMS 任意用户密码重置漏洞
    • ZPanel密码重置
    • PHPYun二次注入
    • Piwigo SQL注入
    • HDwikiSQL注入

    二、CTF例题分析

    1、MD5绕过

    先看代码:

     1 <?php
     2 if (isset($_GET['Username']) && isset($_GET['password'])) {
     3     $logined = true;
     4     $Username = $_GET['Username'];
     5     $password = $_GET['password'];
     6 
     7      if (!ctype_alpha($Username)) {$logined = false;}  //usernam必须为字符型
     8      if (!is_numeric($password) ) {$logined = false;}  //passwor必须为数值型
     9      if (md5($Username) != md5($password)) {$logined = false;}
    10      if ($logined){
    11     			echo "successful";
    12      }else{
    13            echo "login failed!";
    14      }
    15 }
    16 ?>    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    可以看到,这里要传入一个username和password变参数,并且在username不等于password的情况下,让其md5值相等才能绕过。所以这里就需要找一对Md5碰撞。这里由于使用的是”!=“,所以可以找加密后以0e开头的碰撞对来进行绕过。

    这里使用一个碰撞对:

    username= QNKCDZO 
    password= 240610708 
    
    • 1
    • 2

    测试结果:

    在这里插入图片描述

    2、array_srach()绕过

    源码:

    
     if(!is_array($_GET['test'])){exit();}
     $test=$_GET['test'];
     for($i=0;$i<count($test);$i++){
        if($test[$i]==="admin"){//使用强比较判断数组中是否存在admin键值,如果存在,输出error。
            echo "error";
            exit();
        }
         $test[$i]=intval($test[$i]);
     }
     if(array_search("admin",$test)===0){
         echo "flag";
     }else{
        echo "false";
     }
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    分析:这里需要利用array_search()弱类型检索会准换数字类型的特点,由于 $test[$i]=intval($test[$i]);会使数组中的值都变味整形,所以,在array_serch中救护将admin准换为整形进行比较,而admin转换后为“0”,所以我们就需要让传入的数组中不包含admin,并且第一个值经过intval转换后为“0”即可绕过.这里我们直接让数组test[]=0即可绕过。或者我们将数组的第一个设置为任意的不以数字开头的字符串也可以绕过。

    测试结果:

    在这里插入图片描述

    三、实例:HDWIKI鸡肋SQL注入

    漏洞点源码:

    function dofocus(){
                    $doctype = $this->get[2];  //通过GET方式获取doctype
                    switch($doctype){
                            case 2:
                                    $type = 'hot';
                                    $navtitle = $this->view->lang['hotDoc'];
                                    break;
                            case 3:
                                    $type = 'champion';
                                    $navtitle = $this->view->lang['wonderDoc'];
                                    break;
                            default:  //如果 $doctype 不等于2或者3,则会进入默认程序,设置 $docty2=1,所以需要绕过switch检测,不进入默认程序。这里就可以利用switch弱类型绕过。
                        						$doctype = 1;
                                    $navtitle = $this->view->lang['focusDoc'];
                                    $type = 'focus';
                    }
                    $url = 'list-focus-'.$doctype;
                    $this->get[3] = empty($this->get[3]) ? NULL : $this->get[3];
                    $page = max(1, intval($this->get[3]));
                    $start_limit = ($page - 1) * $this->setting['list_focus'];
                    $total=100;
                    $num=10;
                    $count=$this->db->fetch_total('focus',"type=$doctype");  //漏洞点,拼接了$doctype到sql语句中。
                    $count=($count<$total)?$count:$total;
                    $list=$_ENV['doc']->get_focus_list($start_limit,$this-> setting['list_focus'],$doctype);
                    $departstr=$this->multi($count, $this->setting['list_focus'], $page,$url);
                    $this->view->assign('navtitle',$navtitle);
                    $this->view->assign("departstr",$departstr);
                    $this->view->assign('type',$type);
                    $this->view->assign('list',$list);
                    //$this->view->display('list');
                    $_ENV['block']->view('list');
            }
    
    • 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

    所以我们就可以构造$doctype=2 or 1=1来绕过switch检测,从而造成注入。

    四、参考资料

  • 相关阅读:
    GaussDB整体性能慢分析
    MySQL面试题全集,面试必看
    springboot整合log4j2
    ubuntu 下的 使用anaconda 环境运行python 项目
    【C/C++】你知道位段吗?段位?不,是位段!
    C语言实现直接插入排序
    如何评测一个大模型?(微软亚洲研究院 )
    Spring的创建和使用1.0
    ElementUI RUOYI 深色适配
    SpringBoot 自动装配原理
  • 原文地址:https://blog.csdn.net/qq_45590334/article/details/126285755