php反序列化靶场
PHP魔术方法
__construct()→ 创建对象时自动执行;对象被实例化时触发__destruct()→ 对象销毁时自动执行__get($name)→ 读取不存在的属性时调用;用于从不可访问的属性读取数据或者不存在这个键都会调用此方法__set($name, $value)→ 设置不存在的属性时调用;用于将数据写入不可访问的属性__isset($name)→ 对不存在的属性用isset()或empty()时调用__unset($name)→ 对不存在的属性用unset()时调用__call($method, $args)→ 调用不存在的方法时触发;在对象上下文中调用不可访问的方法时触发__callStatic($method, $args)→ 调用不存在的静态方法时触发;在静态上下文中调用不可访问的方法时触发__toString()→ 对象转字符串时调用;把类当作字符串使用时触发__invoke()→ 把对象当函数用时调用;当尝试将对象调用为函数时触发__clone()→ 克隆对象时执行__wakeup()→ 执行unserialize()时,先会调用这个函数__sleep()→ 执行serialize()时,先会调用这个函数
整理了部分更加详细的魔术方法使用,后续会再补充一些
__construct()` → 创建对象时自动执行
PHP允许开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。
__destruct()→ 对象销毁时自动执行析构函数(Destructor)是面向对象编程中一种特殊的成员函数,它在对象生命周期结束时被自动调用,主要工作是清理和释放对象生前占用的资源,如动态分配的内存、打开的文件句柄、网络连接等,以防止资源泄漏,确保程序稳定运行。
也就是说,destruct函数可以在脚本结束前执行其他操作,脚本结束会自动清理内容缓存,destruct可以在脚本清除数据之前做到对数据的保存(转移到另一个文件里),也可以在清理前执行关闭文件之类的,使数据被正确释放
直接执行,然后没有对其他文件连接的断开或者是储存操作,最后清空缓存的时候还是会把做的改变清理掉,完全初始化
在清理前执行断开连接或者是保存之后关闭文件,清理数据就不会再清理文件内的东西了
__get($name)→ 读取不存在的属性时调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class Fruit
{
public $lemon = 'goodLemon';
private $cherry = 'goodCherry';
protected $orange = 'goodOrange';
public function __get($name)
{
return "[__get] $name";
}
}
$fruit = new Fruit();
// 1. public
// → string(5) "goodLemon"
var_dump($fruit->lemon);
// 2. inaccessible(pivate)
// → string(14) "[__get] cherry"
var_dump($fruit->cherry);
// 3. inaccessible(protected)
// → string(14) "[__get] orange"
var_dump($fruit->orange);
// 4. non-existing
// → string(13) "[__get] apple"
var_dump($fruit->apple);lemon是public的属性,所以可以正常读取。
cherry是private,外界基本上是没办法读取的,因此触发了
__get魔术方法。orange是protected,外界基本上是没办法读取的,因此触发了
__get魔术方法。第四个示例的apple,基本上根本没有声明过,所以是不存在的,也触发了
__get魔术方法。__set($name, $value)→ 设置不存在的属性时调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class Fruit
{
public $lemon = '';
private $cherry = '';
protected $orange = '';
public function __set($name, $value)
{
echo "[__set] $name, $value".PHP_EOL;
}
}
$fruit = new Fruit();
// 1. public
// → 正常
$fruit->lemon = 'goodLemon';
// 2. pivate
// → [__set] cherry, goodCherry
$fruit->cherry = 'goodCherry';
// 3. protected
// → [__set] orange, goodOrange
$fruit->orange = 'goodOrange';
// 4. non-existing
// → [__set] apple, goodApple
$fruit->apple = 'goodApple';如示例所示,
lemno是 public 的属性,所以可以正常写入。cherry是 private,外界基本上是没办法写入的,因此触发了__set魔术方法。orange是 protected,外界基本上是没办法写入的,因此触发了__set魔术方法。- 第四个示例的
apple,基本上根本没有声明过,所以是不存在的,也触发了__set魔术方法。
__isset($name)→ 对不存在的属性用isset()或empty()时调用属性重载(这个也在重载目录下)
__unset($name)→ 对不存在的属性用unset()时调用
在给不可访问(protected 或 private)或不存在的属性赋值时,__set() 会被调用。
读取不可访问(protected 或 private)或不存在的属性的值时,__get() 会被调用。
当对不可访问(protected 或 private)或不存在的属性调用isset()或empty()时,__isset() 会被调用。
当对不可访问(protected 或 private)或不存在的属性调用 unset() 时,__unset() 会被调用。
重载方面的不清楚可以重新研究这部分程序
1 |
|
__call($method, $args)→ 调用不存在的方法时触发 在对象中调用一个不可访问方法时,
__call()会被调用。__callStatic($method, $args)→ 调用不存在的静态方法时触发 在静态上下文中调用一个不可访问方法时,
__callStatic()会被调用。- $name 参数是要调用的方法名称。$arguments 参数是一个枚举数组,包含着要传递给方法 $name 的参数。
方法重载
重载
PHP所提供的重载(overloading)是指动态地创建类属性和方法, 当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用。我们是通过魔术方法(magic methods)来实现的。
所有的重载方法都必须被声明为
public。不可访问属性(inaccessible properties)和不可访问方法(inaccessible methods)来称呼这些未定义或不可见的类属性或方法。
__toString()→ 对象转字符串时调用
方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。
有使用的注意事项,有空自己搜出来看吧,料你这会都用不上
__invoke()→ 把对象当函数用时调用当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Sort
{
private $key;
public function __construct(string $key)
{
$this->key = $key;
}
public function __invoke(array $a, array $b): int
{
return $a[$this->key] <=> $b[$this->key];
}
}
$customers = [
['id' => 1, 'first_name' => 'John', 'last_name' => 'Do'],
['id' => 3, 'first_name' => 'Alice', 'last_name' => 'Gustav'],
['id' => 2, 'first_name' => 'Bob', 'last_name' => 'Filipe']
];
// sort customers by first name
usort($customers, new Sort('first_name'));
print_r($customers);
// sort customers by last name
usort($customers, new Sort('last_name'));
print_r($customers);__clone()→ 克隆对象时执行相当于创建副本,使修改内容不去影响原状态
就是独立开成为一个新的对象
__sleep()→serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。 如果该方法未返回任何内容,则 null 被序列化,并产生一个 E_NOTICE 级别的错误。
__wakeup()→__sleep()和__wakeup()是PHP中的两个魔术方法,__sleep()在对象被序列化 (serialize) 之前调用,用于指定要保存的属性(返回属性名数组);而__wakeup()则在对象被反序列化 (unserialize) 之后调用,用于重新初始化对象资源(如数据库连接),常用于数据准备或清理,是处理对象持久化状态的关键。__sleep()
调用时机: 在 serialize() 序列化对象之前。作用: 开发者可以指定哪些属性需要被序列化。
返回值: 必须返回一个包含要序列化属性名称的数组。
用途: 节省空间,忽略大对象、临时资源,或提交未完成的事务。
注意: 如果未返回数组,或返回无效内容(非数组),会产生错误;不能返回父类的私有成员。
__wakeup()调用时机: 在
unserialize()反序列化对象之后,对象构建完成时。作用: 对象被反序列化后,重新初始化对象所需资源。
返回值: 无需返回值。
用途: 重新建立数据库连接,初始化缓存,执行其他设置操作。
注意: 在一些安全场景中(如反序列化漏洞),可以通过修改序列化数据(如属性数量)来尝试跳过
__wakeup()的执行。
序列化反序列化示例
- 序列化
1 |
|
1 | a:3【数组元素个数】:{i:0【数组第几项】;s:4【字符串长度】:"xiao";i:1;s:3:"shi";i:2;s:2:"zi";} |
1 |
|
基础序列化示例
1 | O:4:"test":1:{s:9:"testpub";s:6:"benben";} |
这里$pub变量是私有属性,所以在输出的时候会呈现为类名+属性名,由于私有属性所以在输出的变量前后会相当于有%00所以结果就是在序列化之后得到的数据长度会比类名+属性名多出两个字符长度
如果是public属性的话,输出的时候就只会有属性,没有类名,然后长度就是字符长度
如果是protected属性,输出的时候就是*+属性,但是长度会比*+属性还多两个,是因为实际上是%00+*+%00+属性
这个地方jineng()函数并不是对pub做了一个赋值过程,$this在类成员中是存在的,这里的作用就是读写并输出pub,然后这里就是$pub和pub都是在这个类下面的,输出的时候就是输出了两个部分
但是实际上输出的部分只有一个属性,所以只会在test后呈现为1
$this:当前对象实例,指到,相当于一个指针作用
当前对象实例 = 正在执行方法的那个具体对象
提交最终的序列化对象,序列化也相当于一种编码,可以在执行的时候更简洁
实例学习:
2025geek popself
1 |
|
变量带入是get方式传递给24_SYC.zip变量
非法字符绕过:非法字符会被解析成_(同一个地方栽第二次了,流汗黄豆)
if else语句中有反序列化,所以提交的内容是序列化格式的,提交之后自动序列化
这里
isset()不是魔术方法,这里是检查内容是否为空的作用,并不是魔术方法那个触发payload里相当于你可以提交很多东西,但是提交的内容要去挨个触发上面的函数,才能让回显得出来是flag,就是要挨个去触发函数
所有的变量都在all_in_one类下面,在制作payload时,声明变量需要注意必须写到类名
而且我们不是直接改变量本身,而是声明新的变量然后赋值过去
1
2
3
4$payload = $_GET["24_SYC.zip"];
if (isset($payload)) {
unserialize($payload); // 这里创建对象,但没保存到变量
} // 脚本结束后,这些临时对象被销毁最下面那部分就是一个小的脚本,所以实际上执行结束才是触发的开始,因为一开始的payload,就是通过销毁之后引用其他数据变化过来的
下一步就是md5的比较,满足第二个条件,但是不满足第一个强比较,有一个summer的赋值
执行
$this->QYQS->partner = "summer",__set()魔术方法触发:不是,partner没有一开始的属性定义,直接会触发先触发条件判断中的
$fox()(它调用静态方法,不触发魔术方法),然后触发__call,再触发__toString,最后才触发__invoke。__set()触发之后的if条件判断,$fox instanceof All_in_one:检查$fox是否是All_in_one类的实例,!:逻辑非(取反),要同时满足类里没有变量$fox以及满足$fox()===”summer”;这里的summer是字符串- 在
__set中,首先检查$fox(即obj2->Fox)是否不是All_in_one的实例,并且调用$fox()返回字符串 “summer”。这里$fox是数组["summer", "find_myself"],调用它返回 “summer”,条件满足。 - 这实际上是把”$fox”当成一个函数调用,然后判断它的返回值是不是”summer”。所以 __set 这一块的逻辑:如果Fox 不是 All_in_one 对象,并且Fox 作为函数执行后,返回 “summer”,那么执行;
- 但是这里$fox()赋值为数组(不会触发__invoke()),然后也不属于这个类,然后要求执行之后返回内容是summer,全文的summer是放在一个静态方法里的,就是下面的
find_myself里,要给一个$fox到这个数组 - all_in_one里的$fox是一个实例,并不是数组,所以赋值为数组之后就不属于这个类了
- 在
后面的
$a也会触发__invoke(),但是前面还有个__tostring()魔术方法,调用是把对象转为字符串的时候,也许可以直接交一个变量然后赋值触发?回到if判断下方,执行
$komiko->Eureka($this->L, $this->sleep3r),Eureka方法没有定义,触发__call把
$args[0]就当成一个普通变量,管$arg[1]的值干什么,是直接输出的echo $args[1];是返回值给sleep3r了吗?这里
__call里变量其实提交了两个,就像是C语言的函数调用一样,两个变量分别去对应$this->L和$this->sleep3r,所以实际上需要做赋值处理的就是看参数$args[0]对应的即$this->L,值为 “1e4”的长度是否小于4且加1后大于10000,这是判断条件输出的是
$arg[1],对应的变量就是$this->sleep3r,这里把变量当成字符串输出就会触发__toString__toString中,执行$a = $this->_4ak5ra; $a();,调用__invoke()__invoke中,执行$f($arg),赋值就给要执行的命令,目的是要flag,就去呈现目录,要的就是system(env)关于
__invoke()中$f($arg)会不会触发__invoke()从而陷入循环的问题:这里把
$f和$arg都赋值成一个字符串了,并不是变量,所以不会循环触发使用的指令(需要进行筛选)
cat/flag(或者加上其他flag文件地址之类的)或者env(当flag存在于环境变量)
最后注意:提交的内容是get方式交到url上,所以还需要在序列化之后url编码
PHP反序列化靶场
un-1
1 | <?php |
前置知识:
__wakeup():在对象被反序列化 (unserialize) 之后调用,用于重新初始化对象资源(如数据库连接),常用于数据准备或清理,是处理对象持久化状态的关键。
__destruct():对象销毁时自动执行,destruct可以在脚本清除数据之前做到对数据的保存(转移到另一个文件里),也可以在清理前执行关闭文件之类的,使数据被正确释放
empty():检查是否为空,如果为空则返回TRUE;反之则FALSE;
isset():检查是否存在,如果存在则返回TRUE;反之则FALSE;
strchr():也许这里可以类比C语言,就是检查是否有与search项匹配的部分,因此需要知道strchr的结构
1 | strchr(string,search,before_search); |
这里的string和search是必须的,before_search默认FALSE,如果是是TRUE那么会返回search的参数第一次出现的字符串部分。
dirname(path):参数path是一个包含有指向一个文件的全路径的字符串
- 类名:SoFun
- 变量:file,protected属性
- 注入点:GET变量tryhackme,这是payload的地方
先触发__destruct()
魔术方法触发会让变量被重置,并且由于最后一步是反序列化,所以不能直接避免执行,而是要让它无法执行,让它失效,不满足被执行的原理
当反序列化字符串中声明的对象属性数量大于实际属性数量时,__wakeup()+会被跳过。
所以这里构建两个变量,然后再进行编码
1 |
|
还是有相对路径的问题

Linux默认/
所以直接在windows本地的就失败了
un-2
1 |
|
preg_match():preg_match — 执行匹配正则表达式
show_source():show_source — 别名 highlight_file() — 语法高亮一个文件,打印输出或者返回 filename 文件中语法高亮版本的代码
序列化的结构被过滤也就是:和数字直接连接的结果都会被处理,所以如果没有连接数字就可以
本体比想象的少,这里对序列化内容做url编码之后还会被过滤掉所以需要处理之后再url
所以就是先序列化再处理防止被匹配,再url编码(防止特殊符号)
可以用+5或者5e0代替5
前面部分,类中没有设置变量,所以就不需要在重申类的时候写变量,
1 |
|
之后替换,再编码
un-3
1 |
|
因为存在比较,所以一定会使用已经存在的内容这里需要函数?
变量password和verify全等,所以需要相同赋值,但是不能直接赋值
等一下这里global变量都是未定义的(定义应该是访问权限之类的内容)
类的内部共享同一个属性,所以一开始的时候password和verify是一个值,但是之后对password赋值了,这个时候就不再满足全等,所以这里的方式是将他们绑定为引用关系,这个时候就算改变了也没有影响
主逻辑:
- 如果 GET 参数
tryhackme存在,则反序列化它 - 否则显示源码(所以show_source的那个FILE根本不指五我们需要的文件而是
好吧他的意思就是前面两个全局变量就是未知的,有可能来自一开始包含的这个文件,所以假设为多少也好,假设为空也好,实际上就是相当于两个变量都是未知的
这里的两个全局变量在flag3.php里被定义申明之类的,所以才能有后来的echo的flag
建立引用关系(函数),注意不要漏了函数访问限制的判定
1 |
|
un-4
1 |
|
un-42
1 |
|
ini_set函数详细介绍ini_set是 PHP 的一个内置函数,用于在脚本运行时临时修改php.ini配置选项。它能即时生效,但仅在当前脚本执行期间有效,脚本结束后设置会自动失效。通过ini_set("选项", "新值")的方式,无需重启服务器即可灵活调整参数。功能: 设置指定配置选项的值。该选项会在脚本运行时生效,脚本结束后恢复。
语法:
string ini_set ( string $varname , string $newvalue )返回值: 成功时返回旧值,失败时返回 false。
作用域: 仅在当前脚本运行周期内有效。
会话赋值:如果传入tryhackme 参数,会将其值存入$_SESSION['tryhackme']中;否则显示当前代码。PHP 会话序列化漏洞的核心是:当存储会话数据和读取会话数据时使用不同的序列化处理器,可能导致恶意数据被反序列化执行。
ini_set() 设置 PHP 的会话序列化处理器为php_serialize,会话数据将以 PHP 的默认序列化格式进行存储 GET 变量 tryhackme 的值存储到 $_SESSION 数组中,这个数组最终被序列化 Session:服务器用来存储用户会话状态(比如登录状态、用户 ID、购物车信息)的机制,避免每次请求都重新验证用户
序列化处理器是什么?
PHP 在存储 session 数据时,需要将数组或对象序列化为字符串。序列化处理器决定了如何序列化和反序列化 session 数据。
常见的序列化处理器:
php(默认):使用键名 +|+ 序列化值 的格式- 例如:
user|s:5:"admin";
- 例如:
php_serialize(PHP 5.5.4+):使用标准的serialize()函数格式- 例如:
a:1:{s:4:"user";s:5:"admin";}
- 例如:
php_binary:使用二进制格式
php处理器的具体格式:
当使用 php 处理器时:
1 | $_SESSION['key'] = 'value'; |
反序列化时,会寻找第一个 | 字符:
|左边是键名(key)|右边是值的序列化字符串(serialized value)
| 特点 | unserialize() |
session_start() + php 处理器 |
|---|---|---|
| 触发方式 | 直接调用函数 | 自动反序列化 session 数据 |
| 输入控制 | 通常来自参数 | 来自 session 存储 |
| 利用难度 | 需要找到参数 | 需要控制 session 数据 |
| 常见场景 | 直接反序列化漏洞 | session 反序列化漏洞 |
两个代码联合起来看,有会话被序列化的配置,传入的参数会赋给会话,也就是会话赋值,
un42.php 用 php处理器读取时,会将竖线 | 前面的内容视为键名(这里为空),后面的内容被当作值反序列化,从而执行恶意代码。
利用php处理器不适配,
session 数据默认存储在服务器的临时文件中(比如/tmp/sess_xxxxxx,xxxxxx是 session_id)。
典型的 session 反序列化漏洞,即两个⻚面设置的session 序列化方式不同
这里42是能通过直接创建一个funny的实例 销毁了过后直接就flag了 4这里是储存到会话 然后serialize() 负责 “写入 session 数据”(用php_serialize格式)
php处理器解析 session 数据时,会按第一个 | 把字符串拆成「键名」和「要反序列化的值」|左边被当成空键名,|右边的funny对象序列化字符串会被当成 “值”,触发反序列化。
1 | class funny{ |
全过程 php处理器解析 session 数据:
把a:1:{s:8:”tryhackme”;s:31:”|O:5:”funny”:1:{s:1:”a”;N;}”;}当成普通字符串(因为php处理器不认识php_serialize的数组格式);
按第一个 | 分割:左边是a:1:{s:8:”tryhackme”;s:31:”(被当成键名),右边是O:5:”funny”:1:{s:1:”a”;N;}(被当成 “值”);
PHP 自动对右边的funny对象序列化字符串执行unserialize() → 生成funny对象
un-5
1 |
|
上半部分:要先触发__construct()再触发__destruct()
__construct():创建对象时自动执行,PHP允许开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。
这一部分就是数据销毁前的状态
下半部分:传入内容是字符串,匹配前三十二号和126号后的字符串,所以要把 有效内容放在这个中间,再反序列化
ord() — 转换字符串第一个字节为 0-255 之间的值
chr() — 从数字生成单字节字符串
两个函数互补
所以这里其实不是字符串长度中间的部分过滤而是要控制ASCII值
这里涉及了序列化对私有属性处理的特点:
私有属性在输出的变量前后会相当于有%00,所以结果就是在序列化之后得到的数据长度会比类名+属性名多出两个字符长度
但是这里又要求必须要有有效字符,也就是说需要我们手动编码,这里用十六进制就好
1 | O:5:"funny":1:{S:8:"\00funny\00a";s:10:"givemeflag";} |
然后再url编码
un-6
1 |
|
乍一看总觉得是6和5设置反了,但是6这里有一个$a()把变量当函数调用可以触发一个魔术方法,但是源代码没有,推测需要我们自己构造触发
依旧有判断是不是字符串的,但是我现在暂时还不能确定这个判断到底过滤了什么
还是有触发函数,等会这个$a是不是用来触发pyflag函数的
is_string() — 检测变量的类型是否是字符串
这里和调用引用关系那里还是不一样
$a():PHP会尝试调用 $a 这个变量,如果 $a 是函数名,就调用该函数;如果 $a 是数组 [对象, 方法名],就调用该对象的方法
a自己本身不能作为我们需要的那个函数,pyflag才是我们需要的函数,那就用数组
1 |
|
un-7
1 |
|
先是触发__destruct(),输入action,但是内容有限制,但是没关系还有提交file,但是要检查是否为空,所以不能从if走,要去走else if,所以就是action需要满足upload
但是等等file为空这个可以绕过吗?
is_dir— 判断给定文件名是否是一个目录
mkdir— 创建一个或多个新的目录
长度限制,后缀被闭合,
file_put_contents:把数据写入文件
这是一个 PHP 反序列化漏洞的题目,通过 phar:// 协议触发反序列化来执行 funny 类的 __destruct() 方法,从而输出 flag。
题目本身没有序列化的处理,所以正常来说是不能使用反序列化漏洞的,但是 如果使用 phar:// 伪协议读取一个 PHAR 文件(PHP Archive),PHP 会自动解析该文件中的元数据(Metadata)。而这个元数据是以序列化的形式存储的。 这意味着:file_exists("phar://path/to/file") 等同于 unserialize(metadata)。即使文件后缀是 .txt,只要内容符合 PHAR 格式,phar:// 协议依然能解析它。
对phar的描述是:phar 归档的最佳特征是可以将多个文件组合成一个文件。 因此,phar 归档提供了在单个文件中分发完整的 PHP 应用程序并无需将其解压缩到磁盘而直接运行文件的方法。此外,phar 归档可以像任何其他文件一样由 PHP 在命令行和 Web 服务器上执行。phar 有点像 PHP 应用程序的移动存储器。
包含
flag7.php(定义$flag变量)定义
funny类,其__destruct()方法输出$flag提供两个操作:
action=check:检查文件是否存在action=upload:上传文件(base64 编码内容)
check操作使用file_exists($b),其中$b用户可控可以使用
phar://协议触发反序列化upload操作可以上传任意文件内容 伪协议上传文件
其实需要我们提交两次请求,一次负责检查一次负责上传,先把文件上传了,在之后的检查中才能通过协议触发反序列化
- 构造一个 phar 文件,包含
funny对象 - 通过
upload功能上传该 phar 文件(base64 编码) - 通过
check功能使用phar://协议访问上传的文件,触发反序列化
创建phar文件
1 |
|
1 | GET /target.php?action=upload&data=[base64编码的phar文件内容] |
会返回类似:Your file path:./upload/1234.txt路径展示
./:当前目录
发送请求:
1 | GET /un7.php?action=check&file=phar://./upload/1234.txt |
此时 file_exists() 会解析 phar 文件,反序列化 metadata 中的 funny 对象,触发 __destruct() 方法输出 flag。
un-8
1 |
|
还有数组
__call($method, $args):调用不存在的方法时触发,在对象中调用一个不可访问方法时,__call() 会被调用。
__toString():对象转字符串时调用,方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。
读取不可访问(protected 或 private)或不存在的属性的值时,__get() 会被调用。filename和string
array_walk — 使用用户自定义函数对数组中的每个元素做回调处理
有三个类,其中有很多个魔术方法,找触发开始和结束的链子,结束在函数resolve,返回flag,这个和popself那个题一样,这次打算换向理一理
- 首先是看到flag的位置,想要得到flag需要满足等式,在payload中需要自定义一个函数使等式满足需要触发
resolve()函数, - 函数内两个变量的赋值倒是比较直接,只需要满足值相等就可以了,那这个函数是必须要存在的,不能作为
__call()的触发点 - 但是这里刚好看到
__call()这里,有陌生的函数,感觉是自定义函数的表示,所以应该是有两个自定义函数,然后一个是不存在的,另一个是需要做赋值使满足比较的 - 比较里是一个变量和一个数组,要求数组的第一个是system,也就是说其实可以在这个数组里放入一些其他东西
- 函数用add(),类c里有数组,还需要有一个无效函数触发,对,那就是add无效,但是
addme有效,所以无效函数就是add()
1 | class a { |
- 属性:
public $object:公开属性,可存储任意值。
- 方法:
resolve():使用array_walk遍历当前对象($this)。回调函数检查每个属性的键($prev)是否为"ls",且值($fn)的第一个元素是否为"system"。条件满足时,通过global $flag引入全局变量$flag并输出。__destruct():析构函数,在对象销毁时自动调用。尝试调用$this->object的add()方法,使用@抑制错误。__toString():当对象被作为字符串使用时自动调用,返回$this->object->string的值。
1 | class b { |
- 属性:
protected $filename:受保护属性,仅在类内部或子类中可访问。
- 方法:
addMe():受保护方法,返回包含$filename的字符串。__call($func, $args):当调用不可访问的方法(如受保护或不存在的方法)时触发。使用call_user_func调用方法名加上"Me"后缀的方法(例如调用add则实际调用addMe),并将$args作为参数传递。
这里能看出来需要利用的函数可以是add()
1 | class c { |
- 属性:
private $string:私有属性,仅在类内部可访问。
- 方法:
__construct($string):构造函数,初始化$string属性。__get($name):当访问不可访问的属性(如私有属性)时触发。将$this->$name赋值给$var,然后尝试将$var作为数组,并调用$var[$name]()。
这里,数组也出现了
如果要触发就有现成的可以直接用string
array_walk($array, $callback):遍历数组或对象,对每个元素应用回调函数。回调函数接收值($fn)和键($prev)作为参数。call_user_func($callback, $parameters):调用回调函数,第一个参数为可调用的回调,第二个为参数数组。unserialize($data):将序列化的字符串转换回PHP值(可能包含对象实例)。highlight_file($filename):输出文件内容,并高亮显示PHP语法。global $var:在函数内部引入全局作用域的变量$var。@错误控制运算符:抑制表达式可能产生的错误信息。
