{"schema":"threatline.record/2","record":{"id":"GHSA-w8j8-jv6j-3m47","aliases":["GHSA-w8j8-jv6j-3m47"],"summary":"Any authenticated user can exhaust server memory via a compressed remote image in a chat message","details":"# Any authenticated user can exhaust server memory via a compressed remote image in a chat message\r\n\r\n## Summary\r\nAn ordinary account can put any URL in the image field of a chat message. The server fetches that URL and inlines the bytes into the request it sends on to the model provider. The fetch had no ceiling on how much it would read, and HTTP content encoding lets the sender decide how much the receiver materialises, so a few hundred kilobytes crossing the network turn into hundreds of megabytes held in the server process. Nothing on that path limited the size, the compression ratio, or how often it could be repeated.\r\n\r\n## Preconditions\r\n- Default configuration. Nothing needs to be enabled and no setting needs to be weakened.\r\n- Any verified account with access to a model. No elevated role and no workspace permission.\r\n- A host the attacker controls, reachable from the server, returning a compressed response. The URL has to resolve to a public address: the private-address filter is untouched and still blocks internal targets.\r\n- Every build from 0.6.42 through 0.11.3 carries the affected code.\r\n\r\n## Impact\r\nAvailability, against every other user of the instance. Measured against a real instance: a single request from an ordinary account, with about 261 KB leaving the attacker's host, drove the server process from 731 MB to 2.16 GB resident and returned HTTP 200. That figure is a setting rather than a ceiling, because the size is chosen by the attacker's host and the request can be repeated. There is no confidentiality or integrity effect, and no data is read or altered.\r\n\r\n## Fix\r\nFixed in 0.11.4 by #29623. The image fetch now reads the response incrementally, asks for an unencoded body, and refuses any response that still arrives compressed, so the sender can no longer pick the decoded size. An image that arrives compressed is not inlined and the message is sent on without it. Upgrading is sufficient, there is nothing an operator has to configure.\r\n\r\n## Root cause\r\n- `backend/open_webui/utils/files.py`, `get_image_base64_from_url`: read the whole response body into memory before encoding it.\r\n- `backend/open_webui/utils/middleware.py`, `convert_url_images_to_base64`: the caller, which runs on every chat request and holds each encoded image for the life of that request.\r\n\r\nThe helper was written to fetch a small image and treats a response as something that arrives whole. The announced length was never the constraint that mattered, because content encoding decides how much the receiver has to materialise. The HTTP client library decompresses transparently, so a small compressed body expands to an attacker-chosen decoded size in the server process before any size check applies. The same unbounded read is present in both implementations of the helper, the one using the synchronous HTTP client before 0.9.0 and the asynchronous one after it.\r\n\r\n## Proof of concept\r\nAgainst a real instance, as an account with the default user role:\r\n\r\n1. Host a server that returns a gzip-encoded response and writes the compressed bytes in small pieces rather than all at once.\r\n2. As the ordinary account, send a chat message containing an image item whose URL points at that server.\r\n3. The request returns 200.\r\n\r\nMeasured on that run: 260,934 bytes left the attacker's host, the server decoded 268,435,456 bytes, and the process grew from 730,980,352 to 2,163,429,376 bytes resident over 2.6 seconds.\r\n\r\n## Credits\r\n@y4rvin, for the finding.","categories":["Unclassified"],"product":"open-webui","vendor":"","ecosystem":null,"affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/open-webui/open-webui/security/advisories/GHSA-w8j8-jv6j-3m47","source_modified_at":"2026-09-27T19:49:07Z","fetched_at":"2026-10-24T23:06:24.700873Z","package":{"ecosystem":"pip","name":"open-webui"},"range":">= 0.6.42, < 0.11.4"}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/open-webui/open-webui/security/advisories/GHSA-w8j8-jv6j-3m47","source_modified_at":"2026-09-27T19:49:07Z","fetched_at":"2026-10-24T23:06:24.700873Z","package":{"ecosystem":"pip","name":"open-webui"},"version":"0.11.4"}],"cvss":6.5,"severity":"Medium","kev":{"listed":false},"epss":{"probability":null,"percentile":null,"score_date":null},"published_at":"2026-09-27T19:49:07Z","modified_at":"2026-09-27T19:49:07Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/open-webui/open-webui/security/advisories/GHSA-w8j8-jv6j-3m47","fetched_at":"2026-10-24T23:06:24.700873Z","source_modified_at":"2026-09-27T19:49:07Z"}],"sources":["GitHub Advisory Database"],"references":[],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":null,"confidence":"unknown","reviewed":false},"change_timestamp":"2026-09-27T19:49:07Z","content_hash":"9b5353ca64fc317258148c250a6e5487670d135858c6e5d1eea59140db9bac62"}}
