CTF_Web:反序列化学习笔记(一)php中的类与对象

0x00 前言

前期第一次遇到反序列化这方面题目的时候,也看了不少资料,都是前辈们写的总结,但是都是直接从在ctf中的运用开始的,自己在这段时间整理的过程中,发现对于php类与对象了解不是很多,导致在看一些题目、或值前辈的总结时都比较困难,下面参考php文档,结合自己对php类与对象的理解先把反序列化的基础知识做一下整理。

0x01 php类与对象

class

在php手册中这样介绍:

每个类的定义都以关键字 class 开头,后面跟着类名,后面跟着一对花括号,里面包含有类的属性与方法的定义。
类名可以是任何非 PHP保留字 的合法标签。在这一点中与变量定义相同,避免歧义。一个合法类名以字母或下划线开头,后面跟着若干字母,数字或下划线。以正则表达式表示为: ^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$。一个类可以包含有属于自己的常量,变量(称为“属性”)以及函数(称为“方法”)。

<?php
class SimpleClass
{
    // 声明属性
    public $var = 'a default value';

    // 声明方法
    public function displayVar() {
        echo $this->var;
    }
}
?>

当一个方法在类定义内部被调用时,有一个可用的伪变量 $this$this 是一个到当前对象的引用。此时所有类内的$this都表示当前这个类,访问的对象与方法也是当前类的。例如 $this->var;

new

对定义的类进行创建对应的实例,必须使用new方法,new方法在创建一个实例时,不管以构造函数还是直接传入的方式,其必须为类内的属性赋初始值。当没有参数要传递给类的构造函数,类名后的括号则可以省略掉。
例如对于上面的类:

<?php
$instance = new SimpleClass();
// 也可以这样做:
$className = 'SimpleClass';
$instance = new $className(); // new SimpleClass()
//或不加括号
$instance = new $className;
?>

变量与方法

在php中类的属性和方法存在于不同的“命名空间”中,也就是说一个类的属性和方法可以使用同样的名字。 在类中访问属性和调用方法使用同样的操作符,具体是访问一个属性还是调用一个方法,取决于你的上下文,即用法是变量访问还是函数调用。
例如:

<?php
class Foo
{
    public $bar = 'property';

    public function bar() {
        return 'method';
    }
}

$obj = new Foo();
echo $obj->bar, PHP_EOL, $obj->bar(), PHP_EOL;//第一个访问类属性成员bar,第二个则访问类方法bar()。

属性

类的变量成员叫做“属性”,属性声明是由关键字 publicprotected 或者private开头,然后跟一个普通的变量声明来组成。属性中的变量可以初始化,但是初始化的值必须是常数,这里的常数是指 PHP 脚本在编译阶段时就可以得到其值,而不依赖于运行时的信息才能求值。

  • 被定义为公有的类成员可以在任何地方被访问。
  • 被定义为受保护的类成员则可以被其自身以及其子类和父类访问。
  • 被定义为私有的类成员则只能被其定义所在的类访问。

而在类的成员方法里面,可以用 ->(对象运算符):$this->property(其中 property 是该属性名)这种方式来访问非静态属性。静态属性则是用 ::(双冒号):self::$property 来访问。这里的静态,一定是被static声明过的属性。::后的$符不可省略。
当调用的方法不是静态时会抛出错误:
Non-static method Foo::bar() should not be called statically

<?php
class Foo
{
    public static $bar = 'property';

}
$obj = new Foo();
echo $obj::$bar;

类常量

可以把在类中始终保持不变的值定义为常量。在定义和使用常量的时候不需要使用 $ 符号。且常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用。

<?php
class MyClass
{
    const constant = 'constant value';

    function showConstant() {
        echo  self::constant . "\n";
    }
}

echo MyClass::constant . "\n";//注意这里没有$,与上面做对比。

当使用定义的变量$boj->constant时,会返回 Undefined property: MyClass::$constant,可见对象运算符->中不需要$是因为在访问时自动认为属性为变量。

0x02 构造函数与析构函数

具有构造函数的类会在每次创建新对象时先调用此方法,非常适合在使用对象之前做一些初始化工作。其基本格式为:
__construct(mixed ...$values = ""): void
而析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。他的基本格式为:
__destruct(): void
例如:

<?php

class MyDestructableClass 
{
    function __construct() {
        echo "In constructor\n","<br/>";
    }

    function __destruct() {
        print "Destroying " . __CLASS__ . "\n";
    }
}

$obj = new MyDestructableClass();

运行结果为:

In constructor
Destroying MyDestructableClass 

可见在整个脚本结束后析构函数会被自动调用,需要注意的是在使用exit()终止脚本运行时也会被调用。但在析构函数中调用 exit()会中止其余关闭操作的运行。

0x03 PHP魔术方法

PHP将所有以 __(两个下划线)开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以__为前缀。且所有的魔术方法 必须 声明为 public。其中,魔术方法有:

__construct()
__destruct()
__call()
__callStatic()
__get()
__set()
__isset()
__unset()
__sleep()
__wakeup()
__serialize()
__unserialize()
__toString()
__invoke()
__set_state()
__clone()
__debugInfo()等方法
  • __construct()__destruct()为构造与析构函数,在对象被创建和销毁时调用,上面也已经介绍过了,不再多加介绍。
  • __call()__callStatic()为重载方法时调用,也就是说当调用一个当前类没有的方法时,将会调用他们(静态为callStatic)。

public __call(string $name, array $arguments): mixed·在对象中调用一个不可访问方法时,__call()会被调用。
public static __callStatic(string $name, array $arguments): mixed在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。

  • __set()、__get()、__isset()和__unset()为重载属性时调用,也就是说在访问、赋值一个不可访问、不存在的属性时,将会调用,这些魔术方法的调用时机就是我们需要掌握的内容。

public __set(string $name, mixed $value): void,在给不可访问(protected 或 private)或不存在的属性赋值时,__set()会被调用。
public __get(string `$name`): mixed,读取不可访问(protected 或 private)或不存在的属性的值时,__get()会被调用。
public __isset(string $name): bool,当对不可访问(protected 或 private)或不存在的属性调用 isset()或 empty() 时,__isset()会被调用。
public __unset(string $name): void,当对不可访问(protected 或 private)或不存在的属性调用 unset() 时,__unset()会被调用。

  • __sleep()、__wakeup()为序列化与反序列化之前调用的函数:

serialize()函数会检查类中是否存在一个魔术方法__sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。
unserialize()会检查是否存在一个__wakeup()方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。__wakeup()经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。

  • __serialize()、__unserialize()为序列化与反序列化之前调用的函数,与 __sleep()、__wakeup()不同,他们的优先级最高,即如果同时存在__serialize()、__sleep()函数,只会调用__serialize()__sleep()会被忽略。__unserialize()同理。

public __serialize(): arrayserialize()函数会检查类中是否存在一个魔术方法__serialize()。如果存在,该方法将在任何序列化之前优先执行。它必须以一个代表对象序列化形式的 键/值 成对的关联数组形式来返回,如果没有返回数组,将会抛出一个 TypeError 错误。
public __unserialize(array $data): void,相反,unserialize()检查是否存在具有__unserialize()魔术方法。如果存在,该函数将被传递给__serialize()返回的恢复数组。然后,它可以根据需要从该数组中恢复对象的属性。

这两句话好像比上面所有的话都难以理解,简单说就是__serialize()会把要序列化的键+值变成对应的数组返回,这样其实对于最后序列化的字符串来说一目了然,其实就是换了一种表示方式,那么相反的__unserialize就是把需要反序列化的字串值传递给刚刚序列化之前的数组,是一个逆过程。

  • __toString()为一个类被当成字符串时调用,例如echo、print,此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。
  • __invoke()当尝试以调用函数的方式调用一个对象时自动调用。(只在 PHP 5.3.0 及以上版本有效。)

0x04 序列化与反序列化

serialize(mixed $value): stringserialize()返回字符串,此字符串包含了表示 value 的字节流,可以存储于任何地方。
在下面的代码中:

<?php
class MyDestructableClass 
{
    public $a = "123";
    public $b = "456";
}
$obj = new MyDestructableClass();
echo serialize($obj);

返回的字符串格式为:
O:19:"MyDestructableClass":2:{s:1:"a";s:3:"123";s:1:"b";s:3:"456";}
从左至右的字串一次表示为:
序列化对象O、名称长度19、名称内容MyDestructableClass、属性个数2、第一个属性类型s长度1、第一个属性名称a、第一个属性a的值类型s长度3、第一个属性a的值内容123,第二个属性同理。
反序列化就是将上面那段话的意思反过来,生成一个有属性的对象。使用 unserialize()函数。

0x05 小结

对php类与对象学习之后,再对之前遇到的题目进行学习就会知其然也知其所以然,了解各类型魔术方法的调用时机,就能很好的利用好他们,才会有自己的思路,下面将对典型的几道题目进行练习,学习CTF中的这类题目解题套路。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容