Check MIME types for file uploads

See: #201
This commit is contained in:
Yannik Rödel 2025-10-29 03:11:21 +01:00
parent 8cac73c331
commit 6ec1f1ab30
5 changed files with 14 additions and 6 deletions

View file

@ -30,7 +30,7 @@ ARG SITE
RUN --mount=type=cache,target=/root/.cache/pip \ RUN --mount=type=cache,target=/root/.cache/pip \
apk add --no-cache lighttpd && \ apk add --no-cache lighttpd && \
python -m pip install legacy-cgi itsdangerous requests python -m pip install legacy-cgi itsdangerous requests python-libmagic
COPY --from=build /build/dist /www/ COPY --from=build /build/dist /www/
COPY cgi-bin /cgi-bin/ COPY cgi-bin /cgi-bin/

View file

@ -16,6 +16,7 @@ from urllib.parse import urljoin
import cgi import cgi
import itsdangerous import itsdangerous
import requests import requests
import magic
def fail(status: str, reason: str) -> None: def fail(status: str, reason: str) -> None:
@ -38,6 +39,7 @@ HONEYPOT_FIELD_NAME = "addressline1"
# This regex merely validates what the in-browser form validation already checks and # This regex merely validates what the in-browser form validation already checks and
# isn't all too strict. # isn't all too strict.
EMAIL_REGEX = re.compile(r"^[^ ]+@[^ ]+\.[^ ]+$") EMAIL_REGEX = re.compile(r"^[^ ]+@[^ ]+\.[^ ]+$")
VALID_MIME_TYPES = ("image/jpeg", "image/png", "application/pdf")
# Mapping from site-defined devices (see sites/angestoepselt/_data/config.json in this # Mapping from site-defined devices (see sites/angestoepselt/_data/config.json in this
# repository) to the corresponding Zammad categories: # repository) to the corresponding Zammad categories:
@ -125,6 +127,7 @@ match os.environ.get("REQUEST_METHOD", "").upper():
print(f'<div class="h-captcha" data-sitekey="{os.environ.get("HCAPTCHA_SITE_KEY", "")}"></div>') print(f'<div class="h-captcha" data-sitekey="{os.environ.get("HCAPTCHA_SITE_KEY", "")}"></div>')
print(f'</div>') print(f'</div>')
else: else:
line = re.sub(r"<!--\s*input_accept\s*-->", f'accept="{', '.join(VALID_MIME_TYPES)}"', line, flags=re.IGNORECASE)
print(line) print(line)
exit(0) exit(0)
@ -169,7 +172,12 @@ def get_form_value(
or not value_object.file or not value_object.file
): ):
fail("400 Bad Request", f"Invalid value for field: {name}") fail("400 Bad Request", f"Invalid value for field: {name}")
return (value_object.filename or "upload"), value_object.file.read() data = value_object.file.read()
with magic.Magic() as magic_instance:
mime_type = magic_instance.from_buffer(data)
if mime_type not in VALID_MIME_TYPES:
fail("400 Bad Request", f"Invalid MIME type {mime_type} for upload: {name}")
return (value_object.filename or "upload"), data
else: else:
try: try:
result = cast(form.getfirst(name)) result = cast(form.getfirst(name))
@ -395,7 +403,7 @@ try:
print("Status: 302 Found") print("Status: 302 Found")
print("Content-Type: text/html") print("Content-Type: text/html")
print("Location: /kontakt/fertig") print(f"Location: /kontakt/fertig")
print("") print("")
except Exception as e: except Exception as e:
fail("500 Internal Server Error", str(e)) fail("500 Internal Server Error", str(e))

View file

@ -68,7 +68,7 @@ eingescannt als PDF.
<label class="form-input"> <label class="form-input">
<span>Nachweis hochladen:</span> <span>Nachweis hochladen:</span>
<input type="file" name="document" required /> <input type="file" name="document" required <!-- INPUT_ACCEPT --> />
</label> </label>
Bitte gib uns jetzt noch deine Anschrift. Das sollte die gleiche sein, die auch Bitte gib uns jetzt noch deine Anschrift. Das sollte die gleiche sein, die auch

View file

@ -32,7 +32,7 @@ kein Problem.
<label class="form-input"> <label class="form-input">
<span>Inventarliste:</span> <span>Inventarliste:</span>
<input type="file" name="inventory" /> <input type="file" name="inventory" <!-- INPUT_ACCEPT --> />
</label> </label>
<label class="form-input"> <label class="form-input">

View file

@ -97,7 +97,7 @@ fülle das [Lastschriftmandat](/assets/Lastschriftmandat.pdf) aus (entweder ausg
</label> </label>
<label class="form-input" data-payment> <label class="form-input" data-payment>
<span>SEPA-Lastschriftmandat:</span> <span>SEPA-Lastschriftmandat:</span>
<input type="file" name="document" /> <input type="file" name="document" <!-- INPUT_ACCEPT --> />
</label> </label>
Noch kurz ein paar Hinweise zum Datenschutz: Noch kurz ein paar Hinweise zum Datenschutz: