PHP高级
序列化和反序列化
序列化:对象(数组)=> 字符串
对象私有化成员会自动添加类名;如果是protected变量则会添加* 号,并且前缀添加空字节
目的:
1.将复杂的数组数据类型转换为字符串,方便数组存库操作
2.对象在网络上传输时
3.对象保存到文件中时
__sleep()
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个 E_NOTICE 级别的错误。
序列化与__sleep()魔法方法的关系:相当于对象与构造函数之间的关系。
__sleep()需要返回一个数组,数组的一个成员对应着对象的一个属性名字,默认为全部属性的名字,而我们自己创建的__sleep()函数可以剔除我们不需要的属性名字,这样在序列化时就不会返回该属性值了。
serialize()函数的参数就是__sleep()函数的返回值。
<?php
class Animal{
public $name;
public $age;
public $height;
function __construct($name,$age,$heigh){
$this->name=$name;
$this->age=$age;
$this->height=$heigh;
}
function __sleep(){
$this->name="小白猫";
return ['name','age',];
}
}
$cat=new Animal("小花猫",5,20);
var_dump(serialize($cat));
?>
反序列化:字符串 => 对象(数组)
__wakeup()
unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。例如重新建立数据库连接,或执行其它初始化操作。
漏洞
当传给 unserialize() 的参数可控时,我们可以通过传入一个精心构造的序列化字符串,从而控制对象内部的变量甚至是函数。unserialize()后会导致__wakeup() 或__destruct()的直接调用,中间无需其他过程。因此最理想的情况就是一些漏洞/危害代码在__wakeup() 或__destruct()中,从而当我们控制序列化字符串时可以去直接触发它们。
步骤
1.寻找 unserialize() 函数的参数是否有我们的可控点
2.寻找我们的反序列化的目标,重点寻找 存在 wakeup() 或 destruct() 魔法函数的类
3.一层一层地研究该类在魔法方法中使用的属性和属性调用的方法,看看是否有可控的属性能实现在当前调用的过程中触发的
4.找到我们要控制的属性了以后我们就将要用到的代码部分复制下来,然后构造序列化,发起攻击
PDO (PHP Data Objects)
PDO 应用在 12 种不同数据库中,方便在多种数据库中切换,预处理语句可以防止 SQL 注入。
连接
<?php
$servername = "localhost";
$username = "handy";
$password = "123456";
try {
$conn = new PDO("mysql:host=$servername;", $username, $password);
echo "连接成功";
}
catch(PDOException $e)
{
echo $e->getMessage();
}
?>
创建数据库
<?php
$servername = "localhost";
$username = "handy";
$password = "123456";
try {
$conn = new PDO("mysql:host=$servername", $username, $password);
// 设置 PDO 错误模式为异常
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = "CREATE DATABASE myDBPDO";
// 使用 exec() ,因为没有结果返回
$conn->exec($sql);
echo "数据库创建成功<br>";
}
catch(PDOException $e)
{
echo $sql . "<br>" . $e->getMessage();
}
$conn = null;
?>
创建数据表
<?php
$servername = "localhost";
$username = "handy";
$password = "123456";
$dbname = "myDBPDO";
try {
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
// 设置 PDO 错误模式,用于抛出异常
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 使用 sql 创建数据表
$sql = "CREATE TABLE MyGuests (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
firstname VARCHAR(30) NOT NULL,
lastname VARCHAR(30) NOT NULL,
email VARCHAR(50),
reg_date TIMESTAMP
)";
// 使用 exec() ,没有结果返回
$conn->exec($sql);
echo "数据表 MyGuests 创建成功";
}
catch(PDOException $e)
{
echo $sql . "<br>" . $e->getMessage();
}
$conn = null;
?>
插入数据
<?php
$servername = "localhost";
$username = "username";
$password = "password";
$dbname = "myDBPDO";
try {
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
// 设置 PDO 错误模式,用于抛出异常
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = "INSERT INTO MyGuests (firstname, lastname, email)
VALUES ('John', 'Doe', 'john@example.com')";
// 使用 exec() ,没有结果返回
$conn->exec($sql);
echo "新记录插入成功";
}
catch(PDOException $e)
{
echo $sql . "<br>" . $e->getMessage();
}
$conn = null;
?>