GHSA-FH4F-47MF-F2GJ
Vulnerability from github – Published: 2026-10-15 20:15 – Updated: 2026-10-15 20:15Summary
The openapi3filter.ValidationHandler (the exported, documented request-validation middleware) hard-wires the legacy router (routers/legacy). That router matches the incoming request method verbatim against a trie built exclusively from upper-cased canonical HTTP methods. A request carrying a method token that is not one of the 9 standard methods — a legitimate-but-unsupported token such as PROPFIND, or a non-canonical case variant such as lowercase get — misses the trie, leaves route == nil, and for an existing static path falls through to PathItem.GetOperation(method), whose default: case calls panic(...). The panic fires inside route resolution, before any authentication runs. This is an unauthenticated, remotely triggerable denial of service (recovered per-connection panic + unbounded error-log growth).
Root Cause
openapi3filter/validation_handler.go:39—ValidationHandler.Load()always constructslegacyrouter.NewRouter(doc); there is no alternative router option on this path.routers/legacy/router.go:109— the request method is used verbatim (method, url := req.Method, ...); there is no canonicalization or rejection of unknown method tokens.routers/legacy/router.go:69-70— the trie is populated withstrings.ToUpper(method)keys taken frompathItem.Operations()(canonicalhttp.Method*constants only).routers/legacy/router.go:141—root.Match(method + " " + remainingPath)misses for a non-canonical/unsupported method →route == nil.routers/legacy/router.go:146-150— in theroute == nilblock,doc.Paths.Value(path)returns non-nil for an existing static path, and control reachespathItem.GetOperation(method).openapi3/path_item.go:174—GetOperation'sswitch methodhas no matching case →default: panic(fmt.Errorf("unsupported HTTP method %q", method)).
Impact
An unauthenticated attacker sends a single request with a non-standard HTTP method token to any existing static path. The handler goroutine panics; Go's net/http recovers the panic per connection (process survives), aborts the connection (client sees a reset / 500), and logs a full stack trace. Repeated requests cause connection churn and unbounded http: panic serving log growth, and the request never reaches authentication. Availability impact (A:L) under the default synchronous ValidationHandler middleware; on non-net/http hosts or when reached on an application goroutine the ceiling is a full crash.
Proof of Concept
Serve any spec with a static path (e.g. /ping) behind openapi3filter.ValidationHandler, then:
printf 'PROPFIND /ping HTTP/1.1\r\nHost: x\r\n\r\n' | nc target 8080 # connection aborted (panic)
printf 'get /ping HTTP/1.1\r\nHost: x\r\n\r\n' | nc target 8080 # connection aborted (panic)
printf 'GET /ping HTTP/1.1\r\nHost: x\r\n\r\n' | nc target 8080 # 200 OK (process survived)
Reproduced end-to-end against a real net/http.Server + ValidationHandler: PROPFIND /ping and lowercase get /ping both abort the connection with a panic at path_item.go:174 (stack: GetOperation <- legacy.FindRoute router.go:150 <- ValidationHandler.validateRequest validation_handler.go:84); the process stays alive and subsequent GET /ping returns 200.
Attack Chain
- Entry: unauthenticated
PROPFIND /ping HTTP/1.1(any known static path). Guard: net/http method validation. Bypass proof: net/http accepts any valid HTTP token as a method —PROPFINDand lowercasegetboth reach the handler. - Route match:
legacy/router.go:141root.Match("PROPFIND /ping"). Guard: trie match. Bypass proof: trie holds only upper-cased canonical methods → miss →route == nil. route == nilblock:router.go:146doc.Paths.Value("/ping")→ non-nil for an existing static path (exact map lookup). Guard:pathItem == nilcheck (l.147). Bypass proof: static path exists → not nil → proceeds.- Sink:
router.go:150pathItem.GetOperation("PROPFIND")→path_item.go:174default: panic(...). - Impact: handler goroutine panics; net/http recovers per connection; request never reaches auth.
Bypass Evidence
- net/http does NOT reject non-standard method tokens — confirmed empirically:
PROPFINDand lowercasegetboth reached the handler and panicked. - No canonicalization/rejection exists between
FindRouteentry andGetOperation;req.Methodis used verbatim (router.go:109). git show v0.144.0:openapi3/path_item.goshowspanic(...)at line 174;git show v0.144.0:routers/legacy/router.goshows verbatim method use and the unguardedGetOperationcall.git log v0.144.0..HEADon these files is empty (no post-tag fix).
Scoping Note
The panic requires the request path to be an exact static (non-templated) key in the served spec. A concrete path against a /{id} template returns a clean 500 (not a panic), because Paths.Value is an exact map lookup. Static paths (/health, /login, /version, /metrics, /ping, /) are ubiquitous in real specs and discoverable from public specs/standard endpoints, so a vulnerable target almost always exists. This makes the finding arguably broader than existing panic advisories, as it needs no special spec construct — any static path suffices.
Affected Versions
<= 0.144.0 (present on the latest release tag v0.144.0; long-standing; no patched version).
Suggested Fix
In the legacy router, canonicalize/validate the request method before route resolution and return ErrMethodNotAllowed/ErrPathNotFound for unknown methods, or make PathItem.GetOperation return an error (nil) for unrecognized methods instead of calling panic.
Reported by zx (Jace) — GitHub: @manus-use
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.144.0"
},
"package": {
"ecosystem": "Go",
"name": "github.com/getkin/kin-openapi"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.145.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-100347"
],
"database_specific": {
"cwe_ids": [
"CWE-20",
"CWE-248"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-15T20:15:48Z",
"nvd_published_at": "2026-10-15T16:17:30Z",
"severity": "MODERATE"
},
"details": "## Summary\nThe `openapi3filter.ValidationHandler` (the exported, documented request-validation middleware) hard-wires the legacy router (`routers/legacy`). That router matches the incoming request method verbatim against a trie built exclusively from upper-cased canonical HTTP methods. A request carrying a method token that is not one of the 9 standard methods \u2014 a legitimate-but-unsupported token such as `PROPFIND`, or a non-canonical case variant such as lowercase `get` \u2014 misses the trie, leaves `route == nil`, and for an existing **static** path falls through to `PathItem.GetOperation(method)`, whose `default:` case calls `panic(...)`. The panic fires inside route resolution, **before any authentication** runs. This is an unauthenticated, remotely triggerable denial of service (recovered per-connection panic + unbounded error-log growth).\n\n## Root Cause\n- `openapi3filter/validation_handler.go:39` \u2014 `ValidationHandler.Load()` always constructs `legacyrouter.NewRouter(doc)`; there is no alternative router option on this path.\n- `routers/legacy/router.go:109` \u2014 the request method is used verbatim (`method, url := req.Method, ...`); there is no canonicalization or rejection of unknown method tokens.\n- `routers/legacy/router.go:69-70` \u2014 the trie is populated with `strings.ToUpper(method)` keys taken from `pathItem.Operations()` (canonical `http.Method*` constants only).\n- `routers/legacy/router.go:141` \u2014 `root.Match(method + \" \" + remainingPath)` misses for a non-canonical/unsupported method \u2192 `route == nil`.\n- `routers/legacy/router.go:146-150` \u2014 in the `route == nil` block, `doc.Paths.Value(path)` returns non-nil for an existing static path, and control reaches `pathItem.GetOperation(method)`.\n- `openapi3/path_item.go:174` \u2014 `GetOperation`\u0027s `switch method` has no matching case \u2192 `default: panic(fmt.Errorf(\"unsupported HTTP method %q\", method))`.\n\n## Impact\nAn unauthenticated attacker sends a single request with a non-standard HTTP method token to any existing static path. The handler goroutine panics; Go\u0027s `net/http` recovers the panic per connection (process survives), aborts the connection (client sees a reset / 500), and logs a full stack trace. Repeated requests cause connection churn and unbounded `http: panic serving` log growth, and the request never reaches authentication. Availability impact (A:L) under the default synchronous `ValidationHandler` middleware; on non-net/http hosts or when reached on an application goroutine the ceiling is a full crash.\n\n## Proof of Concept\nServe any spec with a static path (e.g. `/ping`) behind `openapi3filter.ValidationHandler`, then:\n```\nprintf \u0027PROPFIND /ping HTTP/1.1\\r\\nHost: x\\r\\n\\r\\n\u0027 | nc target 8080 # connection aborted (panic)\nprintf \u0027get /ping HTTP/1.1\\r\\nHost: x\\r\\n\\r\\n\u0027 | nc target 8080 # connection aborted (panic)\nprintf \u0027GET /ping HTTP/1.1\\r\\nHost: x\\r\\n\\r\\n\u0027 | nc target 8080 # 200 OK (process survived)\n```\nReproduced end-to-end against a real `net/http.Server` + `ValidationHandler`: `PROPFIND /ping` and lowercase `get /ping` both abort the connection with a panic at `path_item.go:174` (stack: `GetOperation` \u003c- `legacy.FindRoute` router.go:150 \u003c- `ValidationHandler.validateRequest` validation_handler.go:84); the process stays alive and subsequent `GET /ping` returns 200.\n\n## Attack Chain\n1. Entry: unauthenticated `PROPFIND /ping HTTP/1.1` (any known static path). Guard: net/http method validation. Bypass proof: net/http accepts any valid HTTP token as a method \u2014 `PROPFIND` and lowercase `get` both reach the handler.\n2. Route match: `legacy/router.go:141` `root.Match(\"PROPFIND /ping\")`. Guard: trie match. Bypass proof: trie holds only upper-cased canonical methods \u2192 miss \u2192 `route == nil`.\n3. `route == nil` block: `router.go:146` `doc.Paths.Value(\"/ping\")` \u2192 non-nil for an existing static path (exact map lookup). Guard: `pathItem == nil` check (l.147). Bypass proof: static path exists \u2192 not nil \u2192 proceeds.\n4. Sink: `router.go:150` `pathItem.GetOperation(\"PROPFIND\")` \u2192 `path_item.go:174` `default: panic(...)`.\n5. Impact: handler goroutine panics; net/http recovers per connection; request never reaches auth.\n\n## Bypass Evidence\n- net/http does NOT reject non-standard method tokens \u2014 confirmed empirically: `PROPFIND` and lowercase `get` both reached the handler and panicked.\n- No canonicalization/rejection exists between `FindRoute` entry and `GetOperation`; `req.Method` is used verbatim (router.go:109).\n- `git show v0.144.0:openapi3/path_item.go` shows `panic(...)` at line 174; `git show v0.144.0:routers/legacy/router.go` shows verbatim method use and the unguarded `GetOperation` call. `git log v0.144.0..HEAD` on these files is empty (no post-tag fix).\n\n## Scoping Note\nThe panic requires the request path to be an exact static (non-templated) key in the served spec. A concrete path against a `/{id}` template returns a clean 500 (not a panic), because `Paths.Value` is an exact map lookup. Static paths (`/health`, `/login`, `/version`, `/metrics`, `/ping`, `/`) are ubiquitous in real specs and discoverable from public specs/standard endpoints, so a vulnerable target almost always exists. This makes the finding arguably broader than existing panic advisories, as it needs no special spec construct \u2014 any static path suffices.\n\n## Affected Versions\n`\u003c= 0.144.0` (present on the latest release tag v0.144.0; long-standing; no patched version).\n\n## Suggested Fix\nIn the legacy router, canonicalize/validate the request method before route resolution and return `ErrMethodNotAllowed`/`ErrPathNotFound` for unknown methods, or make `PathItem.GetOperation` return an error (nil) for unrecognized methods instead of calling `panic`.\n\n---\nReported by **zx (Jace)** \u2014 GitHub: @manus-use",
"id": "GHSA-fh4f-47mf-f2gj",
"modified": "2026-10-15T20:15:48Z",
"published": "2026-10-15T20:15:48Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/getkin/kin-openapi/security/advisories/GHSA-fh4f-47mf-f2gj"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-100347"
},
{
"type": "WEB",
"url": "https://github.com/getkin/kin-openapi/commit/f1a5b523b82868d2d9be2e116b4e942892be72c3"
},
{
"type": "PACKAGE",
"url": "https://github.com/getkin/kin-openapi"
},
{
"type": "WEB",
"url": "https://github.com/getkin/kin-openapi/releases/tag/v0.145.0"
}
],
"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:L",
"type": "CVSS_V3"
}
],
"summary": "kin-openapi: Unauthenticated panic (DoS) in openapi3filter legacy router via non-canonical/unsupported HTTP method on a static path"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
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.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
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.