先做个简单的文件上传实例, 利用flask实现
@app.route('/upload', methods=['GET','POST'])
def upload():
if request.files.get('filename'):
file = request.files.get('filename')
upload_dir = os.path.join(os.path.dirname(__file__), 'uploadfile')
dir = os.path.join(upload_dir, file.filename)
with open(dir, 'wb') as f:
f.write(file.read())
# file.save(dir)
return render_template('upload.html', file='上传成功')
else:
return render_template('upload.html', file='选择文件')
然后如果需要读取上传文件,可以利用文件读取里的方式,或者使用flask的自带方法
return send_from_directory(os.path.join(os.path.dirname(__file__), 'uploadfile'), file)
django中实现一个文件上传样例。
def UPLOADFILE(request):
if request.method == 'GET':
return render(request, 'upload.html', {'file':'选择文件'})
elif request.method == 'POST':
dir = os.path.join(os.path.dirname(__file__), '../static/upload')
file = request.FILES.get('filename')
name = os.path.join(dir, file.name)
with open(name, 'wb') as f:
f.write(file.read())
return render(request, 'upload.html', {'file':'上传成功'})
这些样例代码都存在未限制文件大小,未限制文件后缀,保存文件的时候可能会目录穿越造成覆盖。如果未限制大小,利用多线程上传的时候可能会对系统资源进行大量的消耗,从而导致dos的做用。
如果没有限制后缀,会造成文件上传,但在框架中的文件上传跟常规的又有些不一样,我们知道在django中都是需要路由来请求,如果我们只是单纯的上传一个py文件,并不会造成常规的文件上传利用。除非你用eval这种处理了文件。
但也不是百分百没问题,如果使用Apache加Python的环境开发,那就跟常规的网站类似了。
在httpd.conf中配置了对python的解析存在一段AddHandler mod_python .py
。那么通过链接请求的时候,比如http://www.xxx.com/test.py
,就会被解析。
还有一种是文件名的文件覆盖,例如功能需要批量上传,允许压缩包形式上传文件,然后解压到用户资源目录,如果此处存在问题,可能会覆盖关键文件来造成代码执行。比如__init__.py
文件。
@app.route('/zip', methods=['GET','POST'])
def zip():
if request.files.get('filename'):
zip_file = request.files.get('filename')
files = []
with zipfile.ZipFile(zip_file, "r") as z:
for fileinfo in z.infolist():
filename = fileinfo.filename
dat = z.open(filename, "r")
files.append(filename)
outfile = os.path.join(app.config['UPLOAD_FOLDER'], filename)
if not os.path.exists(os.path.dirname(outfile)):
try:
os.makedirs(os.path.dirname(outfile))
except OSError as exc:
if exc.errno != errno.EEXIST:
print("\n[WARN] OS Error: Race Condition")
if not outfile.endswith("/"):
with io.open(outfile, mode='wb') as f:
f.write(dat.read())
dat.close()
return render_template('upload.html', file=files)
else:
return render_template('upload.html', file='选择文件')
以上就是一个上传压缩包并且解压到目录的代码,他会按照解压出来的文件夹和文件进行写入目录。构造一个存在问题的压缩包,上传后可以看到文件并不在uploadfile目录,而在根目录下
>>> z_info = zipfile.ZipInfo(r"../__init__.py")
>>> z_file = zipfile.ZipFile("C:/Users/user/Desktop/bad.zip", mode="w")
>>> z_file.writestr(z_info, "print('test')")
>>> z_file.close()
项目如果被重新启动,就会看到界面输出了test字段。
模块也提供了一种安全的方法来解压,``zipfile.extract替换
zipfile.ZipFile`,但是并不代表`extractall`也是安全的。
对于文件的大小,上传的类型中已经有特定的属性来获取
#django
file.size #获取文件大小,字节
#flask
app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 * 1024 #限制1M大小
对于文件类型,flask给出了完整的限制,利用已有的函数和方式
ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif']) #白名单
app.config['UPLOAD_FOLDER'] = os.path.join(os.path.dirname(__file__), 'uploadfile')
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
@app.route('/upload', methods=['GET','POST'])
def upload():
if request.files.get('filename'):
file = request.files.get('filename')
if file and allowed_file(file.filename):
filename = secure_filename(file.filename) #处理文件名
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return render_template('upload.html', file='上传成功')
else:
return render_template('upload.html', file='不允许类型')
else:
return render_template('upload.html', file='选择文件')
django也可以使用类似如上的写法
ALLOWED_EXTENSIONS = settings.ALLOWED_EXTENSIONS
UPLOAD_FOLDER = settings.UPLOAD_FOLDER
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
def UPLOADFILE(request):
if request.method=='GET':
return render(request,'upload.html')
else:
img=request.FILES.get('filename')
if img.size < 100000 and allowed_file(img.name):
f=open(img.name,'wb')
for line in img.chunks():
f.write(line)
f.close()
return render(request, 'upload.html', {'file':'上传成功'})
else:
return render(request, 'upload.html', {'file':"不允许的类型或者大小超限"})
上面的写法中,明显有一个问题就是没有处理文件名,flask中有secure_filename
,django中并没有这个函数。下面把上面的代码再进一步处理一下,根据验证通过的后缀来修改文件名,如果担心重名可以使用时间戳str(time.time())
:
import uuid
ALLOWED_EXTENSIONS = settings.ALLOWED_EXTENSIONS
MAX_SIZE = settings.MAX_FILE_SIZE
UPLOAD_FOLDER = settings.UPLOAD_FOLDER
def allowed_file(filename):
if '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS:
filext = filename.rsplit('.', 1)[1]
return str(uuid.uuid5(uuid.NAMESPACE_DNS, filename))+"."+filext
else:
return None
def UPLOADFILE(request):
if request.method=='GET':
return render(request,'upload.html')
else:
img=request.FILES.get('filename')
if img.size < MAX_SIZE and allowed_file(img.name):
name = UPLOAD_FOLDER+allowed_file(img.name)
f=open(name,'wb')
for line in img.chunks():
f.write(line)
f.close()
return render(request, 'upload.html', {'file':'上传成功'})
else:
return render(request, 'upload.html', {'file':"不允许的类型或者大小超限"})
使用django自带的文件上传的方式
MEDIA_ROOT = os.path.join(BASE_DIR,'media') #以后会自动将文件上传到指定的文件夹中
MEDIA_URL = '/media/' #以后可以使用这个路由来访问上传的媒体文件
MAX_FILE_SIZE = 2097152 #文件大小
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('', views.IndexView.as_view()), #配置路由
]+static(settings.MEDIA_URL,document_root = settings.MEDIA_ROOT)
#定义model,下面的FileExtensionValidator只在使用表单的使用有用,通过表单验证来限制。
models.FileField(upload_to='%Y/%m/%d',validators=[validators.FileExtensionValidator(['jpg','png'],message='必须是图像文件')], default='')
ALLOWED_EXTENSIONS = settings.ALLOWED_EXTENSIONS
MAX_SIZE = settings.MAX_FILE_SIZE
#定义一个views
class IndexView(View):
def filename(self, file):
if '.' in file and file.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS:
filext = file.rsplit('.', 1)[1]
return str(uuid.uuid5(uuid.NAMESPACE_DNS, file))+"."+filext
else:
return None
def get(self,request):
return render(request,'upload.html')
def post(self,request):
myfile = request.FILES.get('filename')
try:
if myfile.size <= MAX_SIZE and self.filename(myfile.name):
myfile.name = self.filename(myfile.name)
File.objects.create(filename=myfile.name, filext=myfile).save()
return render(request, 'upload.html', {'file':'上传成功'})
else:
return render(request, 'upload.html', {'file':'不允许的类型或大小超限'})
except Exception as e:
return render(request,'upload.html', {'file':"不允许的类型或大小超限"})
这样就可以通过model来控制上传目录,然后还是采用如上的限制和判断标准,至于文件重名,django会自动添加字符串来防止重名文件。
还可以自定义field字段来限制:http://codingdict.com/questions/4840