首页后端开发JAVAPHP 入门基础漏洞

PHP 入门基础漏洞

时间2023-07-06 05:29:01发布访客分类JAVA浏览576
导读:PHP 入门基础漏洞0x01 前言这篇文章还是讲一讲黑魔法为主。基于 php_bugs 来学习吧,也听一些师傅说了,如果不是为了打 CTF,根本没必要学 PHP 了,今天是 2022-8-29;正好 Java 学不进去,过一遍 PHP0x0...

PHP 入门基础漏洞

0x01 前言

这篇文章还是讲一讲黑魔法为主。基于 php_bugs 来学习吧,也听一些师傅说了,如果不是为了打 CTF,根本没必要学 PHP 了,今天是 2022-8-29;正好 Java 学不进去,过一遍 PHP

0x02 PHP 基础函数与特性

file_get_contents

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

  • 举个例子
?php  
echo file_get_contents("test.txt");
      
?>
    

上面的代码将输出:

This is a test file with test text.

isset

isset() 函数用于检测变量是否已设置并且非 NULL。

语法

bool isset ( mixed $var [, mixed $... ] )

参数说明:

  • $var:要检测的变量。

如果一次传入多个参数,那么 isset() 只有在全部参数都被设置时返回 TRUE,计算过程从左至右,中途遇到没有设置的变量时就会立即停止。

  • 举个栗子
?php $var = '';
 // 结果为 TRUE,所以后边的文本将被打印出来。 

if (isset($var)) {
     echo "变量已设置。" . PHP_EOL;
 }
     

// 在后边的例子中,我们将使用 var_dump 输出 isset() 的返回值。 
// the return value of isset(). 

$a = "test";
     $b = "anothertest";
     var_dump(isset($a));
     // TRUE 

var_dump(isset($a, $b));
     // TRUE 

unset ($a);
     var_dump(isset($a));
     // FALSE 

var_dump(isset($a, $b));
     // FALSE 


$foo = NULL;
    
var_dump(isset($foo));
     // FALSE ?>
    

返回值

如果指定变量存在且不为 NULL,则返回 TRUE,否则返回 FALSE。

extract

extract() 函数从数组中将变量导入到当前的符号表。

语法

extract(_array,extract_rules,prefix_)

trim

移除字符串两侧的字符(”Hello” 中的 “He”以及 “World” 中的 “d!”):

?php $str = "Hello World!";
     echo $str . PHP_EOL;
     echo trim($str,"Hed!");
     ?>
    

ereg/preg_match

**mb_ereg**(字符串`$pattern`,字符串`$string`,数组 `&
$matches` = **`null`**):bool

执行与多字节支持的正则表达式匹配。

返回值是 true 或者 false

strcmp

用法

strcmp(str1, str2)
	if(str1  str2) {
    
		return  0;

	}
    

	else if (str1 >
 str2) {
    
		return >
     0;

	}


	else {
    
		return 0;

	}
    

意思是,我们如果使用了 strcmp() 函数,就必须要传入两个变量 ———— str1,str2。

如果 str1 str2,则返回 0;若 str1 > str2,则返回 > 0;如果两者相等,返回 0。

现在我们回来看上面这段源代码,很显然,如果这是一道题目,我们不可能知道 FLAG 是多少,所以无法做到让两者相等,这时候就有了我们很重要的绕过特性!

  • strcmp() 比较出错的时候 —-> 返回 NULL;而返回 NULL 即为返回 0,这时候我们就可以得到 Flag ~

0x03 PHP 常见黑魔法

如果要用一句话概括一下黑魔法的成因,我喜欢把它称之为 PHP 原生特点。

在 PHP 当中,我们在类型比较的过程中会产生很多的奇特现象,正是由于这一些奇特现象,会产生一些成功的绕过手段。

1. strcmp 的绕过

  • 这里我们先看这样的一段代码:

Strcmp.php

?php
	
define('FLAG', 'DrunkCTF{
this_is_arrayCompare_flag}
    ');

if (strcmp($_GET['flag'], FLAG) == 0) {
    
 echo "success, flag:" . FLAG;

}
    

?>

此处 $_GET['flag'] 的意思是从 url 中获取到一个名叫 flag 的 GET 参数。

然后来看我们现在要讲的关键函数 strcmp()

它的用法应该是这样的

strcmp(str1, str2)
	if(str1  str2) {
    
		return  0;

	}
    

	else if (str1 >
 str2) {
    
		return >
     0;

	}


	else {
    
		return 0;

	}
    

意思是,我们如果使用了 strcmp() 函数,就必须要传入两个变量 ———— str1,str2。

如果 str1 str2,则返回 0;若 str1 > str2,则返回 > 0;如果两者相等,返回 0。

现在我们回来看上面这段源代码,很显然,如果这是一道题目,我们不可能知道 FLAG 是多少,所以无法做到让两者相等,这时候就有了我们很重要的绕过特性!

  • strcmp() 比较出错的时候 —-> 返回 NULL;而返回 NULL 即为返回 0,这时候我们就可以得到 Flag ~

绕过手段我们先讲 payload,再来讲原理;payload:

?flag[]=0

成功,如图

原理很简单;?flag[]=0 的意思也就是,我传入的变量名为 flag,但是这个 flag 是一个数组类型的变量,数组怎么可能可以和字符串比较呢?所以此处比较出错,成功返回 NULL,也就是返回 0

2. md5 比较绕过

题目代码如下:

?php
 define('FLAG', 'DrunkCTF{
you_bypass_md5!}
    ');
    
 if (($_GET['s1']) != $_GET['s2'] &
    &
 md5($_GET['s1']) == $_GET['s2']) {
    
	 echo "success, flag is :" . FLAG;

 }
    
?>
    

简单来说,我们的逻辑就是 s1 和 s2 不能相等,但是它们的 md5 值要相等;于是就有了如下两种绕过手段。

绕过一

用科学计数法绕过

‘0e123456789’ == ‘0e987654321’ == 0

以下值在md5加密后以0E开头:

  • QNKCDZO
  • 240610708
  • s878926199a
  • s155964671a
  • s214587387a
  • s214587387a
  • 0e215962017(这个用的非常多

payload 如下

md5.php?s1=QNKCDZO&
    s2=240610708

绕过二

通过数组绕过,也叫数组 trick

原理:

md5([1,2,3]) == md5([4,5,6]) == NULL

所以我们的 payload:

md5.php?s1[]=1&
    s2[]=2

3. extract 变量覆盖绕过

源码如下

?php
	
$flag='xxx';
    
extract($_GET);

 if(isset($shiyan)) {
    
 $content=trim(file_get_contents($flag));

 	if($shiyan==$content) {

 		echo'ctf{
xxx}
    ';

 	}

 	else {
    
 		echo'Oh.no';

 	}

 }
    

?>
    

这里需要实现shiyan==content,content来源于file_get_contents(flag),而这个file_get_contents函数是把文件的信息打印出来,我们这个flag是个变量,他取值必定不是文件名,因此这里content变量的值为空,此时无论怎么写flag,content都为空,我们只需要保证shiyan也为空就可以,构造payload如下

flag=123&
    shiyan=

4. 绕过过滤的空白字符

源码如下

?php
 show_source(__FILE__);
    
$info = "";
     
$req = [];
    
$flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
    
 
ini_set("display_error", false);
     //为一个配置选项设置值
error_reporting(0);
 //关闭所有PHP错误报告
 
if(!isset($_GET['number'])){
    
   header("hint:26966dc52e85af40f59b4fe73d8c323a.txt");
     //HTTP头显示hint 26966dc52e85af40f59b4fe73d8c323a.txt
 
   die("have a fun!!");
 //die — 等同于 exit()
 
}

 
foreach([$_GET, $_POST] as $global_var) {
      //foreach 语法结构提供了遍历数组的简单方式 
    foreach($global_var as $key =>
 $value) {
     
        $value = trim($value);
      //trim — 去除字符串首尾处的空白字符(或者其他字符)
        is_string($value) &
    &
     $req[$key] = addslashes($value);
 // is_string — 检测变量是否是字符串,addslashes — 使用反斜线引用字符串
    }
 
}
 
 
 
function is_palindrome_number($number) {
     
    $number = strval($number);
     //strval — 获取变量的字符串值
    $i = 0;
     
    $j = strlen($number) - 1;
 //strlen — 获取字符串长度
    while($i  $j) {
 
        if($number[$i] !== $number[$j]) {
     
            return false;
 
        }
     
        $i++;
     
        $j--;
 
    }
     
    return true;
 
}
 
 
 
if(is_numeric($_REQUEST['number'])) //is_numeric — 检测变量是否为数字或数字字符串 
{
    
 
   $info="sorry, you cann't input a number!";

 
}

elseif($req['number']!=strval(intval($req['number']))) //intval — 获取变量的整数值
{
    
 
     $info = "number must be equal to it's integer!! ";
  
 
}

else
{
    
 
     $value1 = intval($req["number"]);
    
     $value2 = intval(strrev($req["number"]));
  
 
     if($value1!=$value2){
    
          $info="no, this is not a palindrome number!";

     }

     else
     {

 
          if(is_palindrome_number($req["number"])){

              $info = "nice! {
$value1}
     is a palindrome number!";
 
          }

          else
          {
    
             $info=$flag;

          }

     }

 
}
    
 
echo $info;

我们这里可以分段代码审计一下

首先告诉我们需要传进去一个 GET 请求的参数,名为 number;

if(!isset($_GET['number'])){
    

 header("hint:26966dc52e85af40f59b4fe73d8c323a.txt");
     //HTTP头显示hint 26966dc52e85af40f59b4fe73d8c323a.txt

 die("have a fun!!");
 //die — 等同于 exit()

}

往下看

foreach([$_GET, $_POST] as $global_var) {
     //foreach 语法结构提供了遍历数组的简单方式

 foreach($global_var as $key =>
 $value) {
    

 $value = trim($value);
     //trim — 去除字符串首尾处的空白字符(或者其他字符)

 is_string($value) &
    &
     $req[$key] = addslashes($value);
 // is_string — 检测变量是否是字符串,addslashes — 使用反斜线引用字符串

 }


}
    

这里的代码不难,读取到我们所有的 GET 请求与 POST 请求的参数,进行循环,然后把这个参数里面的空格去掉,这就是过滤空白字符了。

继续往下看 is_palindrome_number() 函数,用来判断是否为回文数。

后续是判断关键,总结一下有以下四个条件

1、if(is_numeric($_REQUEST['number'])) 这个条件需要为假,才能继续往下运行 
2、要求$req['number']==strval(intval($req['number'])) 
3、要求intval($req["number"])==intval(strrev($req["number"]));
    //strrev函数作用是反转字符串 
4、 if(is_palindrome_number($req["number"]))这个条件需要为假,才能输出flag

第一个条件,这里我们不能输入 ?number=1,要不然进不到后续的代码逻辑上,通过 ?number=%001 可以绕过

针对req['number']==strval(intval(req['number']))的话,它这个相当于是不让变量中有字符串,只能有数字时才符合条件,这个是怎么知道的呢,当然是本地测试

?php 
	
show_source(__FILE__);
     
$a=addslashes(trim($_GET['a']));
     
$b=strval(intval($a));
     
var_dump($a==$b);
     

?>
    

一般的话肯定考虑一个空格给它绕过,但是这段代码里传入的变量都经过了 trim 函数,trim 函数过滤了很多空白字符

一般的话当然是没办法了,但是这里还有一个%0c,也就是\f未被过滤,因此这里我们可以用它来进行绕过,我们本地试也可以发现它是符合条件的

现在来看第三个条件 intval(req["number"])==intval(strrev(

来看最后一个,让 if(is_palindrome_number($req["number"])) 条件为假,这个函数定义如下

这个函数当它是回文数时就会正确,因此看似是与条件三矛盾的,但想到还有空白字符,用它的时候同时写回文数,此时是不是就可以成功绕过呢

我们尝试 payload

?number=%00%0c131

成功 getflag

5. ereg/preg_match 正则 %00 截断

  • 源码如下
?php 

$flag = "flag";


if (isset ($_GET['password'])) 
{

  if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
  {
    
    echo 'p>
    You password must be alphanumeric/p>
    ';

  }
    
  else if (strlen($_GET['password'])  8 &
    &
     $_GET['password'] >
 9999999)
   {

     if (strpos ($_GET['password'], '*-*') !== FALSE) //strpos — 查找字符串首次出现的位置
      {
    
      die('Flag: ' . $flag);

      }

      else
      {
    
        echo('p>
    *-* have not been found/p>
    ');
 
       }

      }

     else 
     {
    
        echo 'p>
    Invalid password/p>
    ';
 
      }

   }
     
?>
    

这里的话有三个条件

1、 if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === TRUE)
2、(strlen($_GET['password'])  8 &
    &
     $_GET['password'] >
     9999999)
3、  if (strpos ($_GET['password'], '*-*') !== FALSE)

第一个,就是匹配里面要有字母与数字,没事; 第二个比较要动脑,要求我们的密码值大于 9999999,但是不得超过 8 位,你细品; 第三个让password中包含*-*,这与第一点相悖了,思考这个绕过。

第一个先不考虑,直接看第二个,第二个的绕过很简单,用科学计数法即可。

payload:

?password=1e10

第三个条件这里,要与第一个相结合的绕过:

我们知道当语句遇到%00的时候就会认为是休止符,不再往后看,我们如果在password中添加%00,再添加这个字符串,是不是就可以成功绕过呢,我们构造 payload 如下进行尝试

?password=1e10%00*-*

成功绕过

6. sha()函数比较绕过

源代码如下

?php

$flag = "flag";


if (isset($_GET['name']) and isset($_GET['password'])) 
{
    
    if ($_GET['name'] == $_GET['password'])
        echo 'p>
    Your password can not be your name!/p>
    ';
    
    else if (sha1($_GET['name']) === sha1($_GET['password']))
      die('Flag: '.$flag);
    
    else
        echo 'p>
    Invalid password./p>
    ';

}
    
else
    echo 'p>
    Login first!/p>
    ';
    
?>
    
  • 这里的逻辑简单看一下,很简单,看着就好绕过;要求是传入的 username 和 password 不能相同,但是它们经过 sha1() 算法之后的值要相同。

sha1 算法加密的同样是字符串,那就意味着当值为数组时同样会报错,如果我们让两个都报错,那么他们肯定是同种类型的 Null,此时就可以绕过,正常的话我们会构造 payload 如下

name[]=1&
    password[]=2

7. session 验证绕过

源代码如下

// 08 SESSION验证绕过

?php

$flag = "flag";
    

session_start();
 
if (isset ($_GET['password'])) {
    
    if ($_GET['password'] == $_SESSION['password'])
        die ('Flag: '.$flag);
    
    else
        print 'p>
    Wrong guess./p>
    ';

}
    
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
    
?>

条件是 _GET['password'] == _SESSION['password'],这里 session 中的 password 是不存在的,需要我们自己传值,那我们如果不传的话不就为 Null 了吗,此时我们的 GET 传 password 也传个空,此时两者是不是就相等了呢,我们尝试一下

payload:

?password=

8. urldecode 二次编码绕过

  • 原代码如下
?php
if(eregi("hackerDJ",$_GET['id'])) {
    
  echo("p>
    not allowed!/p>
    ");
    
  exit();

}
    

$_GET['id'] = urldecode($_GET['id']);

if($_GET['id'] == "hackerDJ")
{
    
  echo "p>
    Access granted!/p>
    ";
    
  echo "p>
flag: *****************}
     /p>
    ";

}
    
?>
    

写的和那啥一样,一塌糊涂,这里我修改了一下

// 10 urldecode二次编码绕过

?php

$id = $_GET['id'];


if(preg_match("hackerDJ",$id)) {
    
  echo("p>
    not allowed!/p>
    ");
    
  $flag=false;

}

if ($flag === true) {
    
$m = urldecode($id);

if($m == "hackerDJ")
{
    
  echo "p>
    Access granted!/p>
    ";
    
  echo "p>
flag: *****************}
     /p>
    ";

}

}
    
?>

这样逻辑是对的

代码逻辑:获取 GET 请求中的 id 参数,判断 id 参数是否与 “hackerDJ” 相同,如果相同,寄。 如果不相同,继续往下看,判断 url 编码后 的 id 变量是否与 “hackerDJ” 相同,这个地方需要对 id 的值进行二次编码,因为第一次编码传进去的会经过一次解码,所以会编程 hackDJ;所以我们的 payload 如下

?id=%2568%2561%2563%256b%2544%254a

这道题还是有点二,算了,就当是二次编码的一个学习吧。

9. X-Forwarded-For 绕过指定 IP 地址

这个有点意思,源码如下

?php
function GetIP(){
    
if(!empty($_SERVER["HTTP_CLIENT_IP"]))
    $cip = $_SERVER["HTTP_CLIENT_IP"];
    
else if(!empty($_SERVER["HTTP_X_FORWARDED_FOR"]))
    $cip = $_SERVER["HTTP_X_FORWARDED_FOR"];
    
else if(!empty($_SERVER["REMOTE_ADDR"]))
    $cip = $_SERVER["REMOTE_ADDR"];
    
else
    $cip = "0.0.0.0";
    
return $cip;

}
    

$GetIPs = GetIP();

if ($GetIPs=="1.1.1.1"){

echo "Great! flag is ctf{
*********}
    ";

}

else{
    
echo "错误!你的IP不在访问列表之内!";

}
    
?>
    

做这道题之前不妨先了解一下HTTP_CLIENT_IPX_FORWARDED_FORREMOTE_ADDR

HTTP_CLIENT_IP 是代理服务器发送的HTTP头

HTTP_CLIENT_IP 是代理服务器发送的HTTP头,HTTP_CLIENT_IP确实存在于http请求的header里。

X_FORWARDED_FOR

简称XFF头,它代表客户端,也就是HTTP的请求端真实的IP,只有在通过了HTTP 代理或者负载均衡服务器时才会添加该项,正如上面所述,当你使用了代理时,web服务器就不知道你的真实IP了,为了避免这个情况,代理服务器通常会增加一个叫做x_forwarded_for的头信息,把连接它的客户端IP(即你的上网机器IP)加到这个头信息里,这样就能保证网站的web服务器能获取到真实IP

REMOTE_ADDR

表示发出请求的远程主机的 IP 地址,remote_addr代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,当你的浏览器访问某个网站时,假设中间没有任何代理,那么网站的web服务器(Nginx,Apache等)就会把remote_addr设为你的机器IP,如果你用了某个代理,那么你的浏览器会先访问这个代理,然后再由这个代理转发到网站,这样web服务器就会把remote_addr设为这台代理机器的IP

简单的总结一下就是

$_SERVER['REMOTE_ADDR'];
     //访问端(有可能是用户,有可能是代理的)IP
$_SERVER['HTTP_CLIENT_IP'];
     //代理端的(有可能存在,可伪造)
$_SERVER['HTTP_X_FORWARDED_FOR'];
 //用户是在哪个IP使用的代理(有可能存在,也可以伪造)

尝试伪造 XFF 头来进行绕过

10. intval 函数

  • 这里有四块内容,慢慢讲

int intval(var,base) 的特性

如果 base 是 0,通过检测 var 的格式来决定使用的进制:

  • 如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);否则,
  • 如果字符串以 “0” 开始,使用 8 进制(octal);否则,
  • 将使用 10 进制 (decimal)。

intval 四舍五入绕过

源代码:

?php

if($_GET[id]) {
    
   mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS);
    
  mysql_select_db(SAE_MYSQL_DB);
    
  $id = intval($_GET[id]);
    
  $query = @mysql_fetch_array(mysql_query("select content from ctf2 where id='$id'"));

  if ($_GET[id]==1024) {
    
      echo "p>
    no! try again/p>
    ";

  }

  else{
    
    echo($query[content]);

  }

}
    

?>
    

绕如其名

整体看过后,发现重点大致是这几句

1 、$id = intval($_GET[id]);
    
$query = @mysql_fetch_array(mysql_query("select content from ctf2 where id='$id'"));
    
2、if($_GET[id]==1024)

第二个语句是_GET[id]不等于1024时才往下执行,但好端端的为什么要提到这个1024呢,往下运行是输出查询结果,这是不是间接的说明id为1024时对应的内容为flag呢,因此我们这里去构造一个1024即可,但等于1024又无法往下运行,这个时候就关注到了查询语句中是id,而id是intval(_GET[id]),因此这里就可以用intval的几个特性来绕过了

从官方例子中也可以看出,小数点后不计,那我们这里传值1024.2,在查询时不也是1024吗,而且后面检测是否为1024时还可以绕过检测,因此最终payload为

id=1024.2

绕 preg_match 正则

源代码

?php
include("flag.php");
    
highlight_file(__FILE__);

 
if(isset($_GET['num'])){
    
    $num = $_GET['num'];

    if(preg_match("/[0-9]/", $num)){
    
        die("no no no!");

    }

    if(intval($num)){
    
        echo $flag;

    }

}
    
?>
    

0 - 9 都被过滤了,所以要用数组来绕过

payload:

num[]=1

绕某个具体数字

源代码

include("flag.php");
    
highlight_file(__FILE__);

if(isset($_GET['num'])){
    
    $num = $_GET['num'];

    if($num==="4476"){
    
        die("no no no!");

    }

    if(intval($num,0)===4476){
    
        echo $flag;

    }
else{
    
        echo intval($num,0);

    }

}
    

这关的话就是要求变量值不能为4476,但用过intval函数后为4476,这里的话我们首先需要知道intval的第二个参数为0时的意思是什么

根据这张图绕过

看到这里的话就可以看出payload就有多种构造方法了

num=4476e123
//这里就跟上面那个单引号的1e10情况一样,此时只看字母前面的
num=4476.1
//计算int值时,后面有小数点会直接舍去
num=0x117c
//0x表明是十六进制数,117c是4476的十六进制数
num=010574
//0表明是八进制数,10574是4476的八进制数

payload:

?number=010574

终极绕过

include("flag.php");
    
highlight_file(__FILE__);

if(isset($_GET['num'])){
    
    $num = $_GET['num'];

    if($num==4476){
    
        die("no no no!");

    }

    if(preg_match("/[a-z]|\./i", $num)){
    
        die("no no no!!");

    }

    if(!strpos($num, "0")){
    
        die("no no no!!!");

    }

    if(intval($num,0)===4476){
    
        echo $flag;

    }

}
    
?>
    

这道题的话看着几乎是防死了,多过滤了.,这就意味着小数点绕过行不通,此时我们看到这个i修饰符,想到那个m修饰符,此时就想起来有个换行符%0a,它对实际输出没影响,它还可以绕过上面的那些函数,因此我们这里构造如下语句,就实现了绕过,由于小数点不能用,这里就用八进制

num=%0a010574

11. 十六进制与数字比较

0x04 结合 SQL

1. token 伪造

  • 源代码
?php
    include 'common.php';
    
    $requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);

    //把一个或多个数组合并为一个数组
    class db
    {
    
        public $where;

        function __wakeup()
        {
    
            if(!empty($this->
where))
            {
    
                $this->
    select($this->
    where);

            }

        }

        function select($where)
        {
    
            $sql = mysql_query('select * from user where '.$where);
    
            //函数执行一条 MySQL 查询。
            return @mysql_fetch_array($sql);

            //从结果集中取得一行作为关联数组,或数字数组,或二者兼有返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
        }

    }


    if(isset($requset['token']))
    //测试变量是否已经配置。若变量已存在则返回 true 值。其它情形返回 false 值。
    {
    
        $login = unserialize(gzuncompress(base64_decode($requset['token'])));
    
        //gzuncompress:进行字符串压缩
        //unserialize: 将已序列化的字符串还原回 PHP 的值

        $db = new db();
    
        $row = $db->
    select('user=\''.mysql_real_escape_string($login['user']).'\'');

        //mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。

        if($login['user'] === 'ichunqiu')
        {
    
            echo $flag;

        }
else if($row['pass'] !== $login['pass']){
    
            echo 'unserialize injection!!';

        }
else{
    
            echo "(╯‵□′)╯︵┴─┴ ";

        }

    }
else{
    
        header('Location: index.php?error=1');

    }
    

?>
 

重点的话有以下几处,分别是输出 flag 的地方

if($login['user'] === 'ichunqiu')
        {
    
            echo $flag;

}

这就要求 $login['user']ichunqiu,其次就是它的加密

if(isset($requset['token']))
    //测试变量是否已经配置。若变量已存在则返回 true 值。其它情形返回 false 值。
    {
    
        $login = unserialize(gzuncompress(base64_decode($requset['token'])));
    
        //gzuncompress:进行字符串压缩
        //unserialize: 将已序列化的字符串还原回 PHP 的值
  • 感觉这个绕过就非常简单了,修改 Cookie 里面的 token 就好了。

写个简单的 EXP 绕一下

?php 
	
$a=array(['user']==='ichunqiu');
    
$user =base64_encode(gzcompress(serialize($a)));
     
echo $user;
     

?>
    

2. 密码 md5 比较绕过

?php

//配置数据库
if($_POST[user] &
    &
 $_POST[pass]) {
    
    $conn = mysql_connect("********, "*****", "********");
    
    mysql_select_db("phpformysql") or die("Could not select database");
    
    if ($conn->
connect_error) {
    
        die("Connection failed: " . mysql_error($conn));

}
     

//赋值

$user = $_POST[user];
    
$pass = md5($_POST[pass]);
    

//sql语句

// select pw from php where user='' union select 'e10adc3949ba59abbe56e057f20f883e' # 

// ?user=' union select 'e10adc3949ba59abbe56e057f20f883e' #&
    pass=123456

$sql = "select pw from php where user='$user'";
    
$query = mysql_query($sql);

if (!$query) {
    
    printf("Error: %s\n", mysql_error($conn));
    
    exit();

}
    
$row = mysql_fetch_array($query, MYSQL_ASSOC);
    
//echo $row["pw"];
    

  if (($row[pw]) &
    &
 (!strcasecmp($pass, $row[pw]))) {
    

//如果 str1 小于 str2 返回  0; 如果 str1 大于 str2 返回 >
     0;如果两者相等,返回 0。


    echo "p>
    Logged in! Key:************** /p>
    ";

}

else {
    
    echo("p>
    Log in failure!/p>
    ");


  }

}
    
?>
    

条件是 if ((row[pw]) & & (!strcasecmp(row[pw]))),而这个 row[pw] 是从根据SQL语句从数据库中查询出来的,因此前面这个也就是说要在数据库中存在这个 SQL 语句对应的密码,而后面的就是校验了,看你输入的密码与数据库是否匹配,这个时候就想到了 union select 可以自己创一行数据

本地测试如下图

mysql>
     select * from users where username='' union select 1,2,3;
    
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | 2        | 3        |
+----+----------+----------+
1 row in set (0.00 sec)

mysql>
     select * from users ;
    
+----+----------+------------+
| id | username | password   |
+----+----------+------------+
|  1 | Dumb     | Dumb       |
|  2 | Angelina | I-kill-you |
|  3 | Dummy    | p@ssword   |
|  4 | secure   | crappy     |
|  5 | stupid   | stupidity  |
|  6 | superman | genious    |
|  7 | batman   | mob!le     |
|  8 | admin    | admin      |
|  9 | admin1   | admin1     |
| 10 | admin2   | admin2     |
| 11 | admin3   | admin3     |
| 12 | dhakkan  | dumbo      |
| 14 | admin4   | admin4     |
+----+----------+------------+
13 rows in set (0.00 sec)

从两个查询语句中可以看出,这个union select查询的语句明显是不存在在数据表中的,它取决于我们union select后面输入的东西的

mysql>
     select * from users where username='' union select 1,2,database();
    
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | 2        | security |
+----+----------+----------+
1 row in set (0.00 sec)

那么如果我们用union select的话,这是不是就意味着password可控呢

mysql>
     select * from users where username='' union select 1,2,123456;
    
+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | 2        | 123456   |
+----+----------+----------+
1 row in set (0.00 sec)

此时查询结果,取出password,那肯定就是123456了,由于密码提交的时候有pass = md5(_POST[pass]); ,所以我们提交123456,到检验时中就变成了e10adc3949ba59abbe56e057f20f883e(md5加密后的123456),那我们这个时候不就无法做到对应了吗,不过我们是不是可以把md5加密后的密码放到SQL语句中呢,这样比对的时候不就一致了吗,因此构造payload如下

user=' union select 1,2,'e10adc3949ba59abbe56e057f20f883e' # &
    password=123456

3. sql闭合绕过

简单的警号绕过,源码如下

?php


if($_POST[user] &
    &
 $_POST[pass]) {
    
    $conn = mysql_connect("*******", "****", "****");
    
    mysql_select_db("****") or die("Could not select database");
    
    if ($conn->
connect_error) {
    
        die("Connection failed: " . mysql_error($conn));

}
     
$user = $_POST[user];
    
$pass = md5($_POST[pass]);
    

//select user from php where (user='admin')#

//exp:admin')#

$sql = "select user from php where (user='$user') and (pw='$pass')";
    
$query = mysql_query($sql);

if (!$query) {
    
    printf("Error: %s\n", mysql_error($conn));
    
    exit();

}
    
$row = mysql_fetch_array($query, MYSQL_ASSOC);
    
//echo $row["pw"];

  if($row['user']=="admin") {
    
    echo "p>
    Logged in! Key: *********** /p>
    ";

  }


  if($row['user'] != "admin") {
    
    echo("p>
    You are not admin!/p>
    ");

  }

}
    

?>
    

payload:

username=admin')#

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!

php基础漏洞入门字符串

若转载请注明出处: PHP 入门基础漏洞
本文地址: https://pptw.com/jishu/291341.html
php设计模式(十二):外观模式(Facade) PortSwigger-文件上传

游客 回复需填写必要信息