pwneglyph logo
web python mime-confusion file-upload content-type xss

Control the served Content-Type by storing a filename whose URL-like suffix changes the guessed MIME.

mimetypes.guess_type() Confused by Pseudo-URL Filenames

mimetypes.guess_type() is not a content detector — it infers type from the string and historically treats it with URL-like semantics. Query-string-looking suffixes can change the guessed MIME, so the app serves disk bytes as whatever the name implies, even when the extension or content says otherwise.

Why It Works

  • Security logic assumes "if MIME starts with image/, browser execution risk is gone" — but the MIME came from a manipulable filename.

Vulnerable Pattern

  • Upload/move endpoints where the attacker controls the stored filename, and the download route sets Content-Type from mimetypes.guess_type(filename) rather than content or a fixed allowlist.

Exploit Flow

  1. Test whether the route preserves unusual filenames containing ?, %3f, fragments, or multi-extension forms.
  2. Verify both what the server stores on disk and what MIME the download route emits.
  3. Align the served MIME with the browser sink you need — a script include, an SVG load, a JSON fetch.

Variations

  • test.js?image.svg, foo.html?.png, bar.svg#x.js — depending on which parser runs at each step.

Common Blockers

  • The filesystem path may strip or reject ?, or the download route may canonicalize names before MIME guessing.

PoC Sketch

curl -F 'file=@poc.js;filename=test.js?image.svg' https://target/api/upload

Good Situations To Use It

  • An upload route lets you keep odd characters in the stored name.
  • The download route derives Content-Type from the name via guess_type.
  • You need a specific MIME to reach a browser sink.

Sources

  • breizhctf2026/web/no_thanks_i_use_ai