GHSA-W4C6-7R69-W7J9

Vulnerability from github – Published: 2026-06-05 16:41 – Updated: 2026-06-05 16:41
VLAI
Summary
klever-go: REST API slow-header connection exhaustion via Gin Engine.Run
Details

Summary

The Klever seednode REST API starts a Gin engine with Engine.Run(restAPIInterface). In Gin v1.9.1, Engine.Run calls Go's default http.ListenAndServe, which constructs an HTTP server without application-level ReadHeaderTimeout, ReadTimeout, or MaxHeaderBytes limits.

An unauthenticated client that can reach a REST listener bound with Klever's documented --rest-api-interface :8080 all-interface option can hold incomplete HTTP headers open indefinitely. In a local proof against the real cmd/seednode/api.Start path on v1.7.17, 120 slow-header connections caused 20/20 legitimate /log probes to fail with accept: too many open files. A fixed control using the same Gin router behind an explicit http.Server with ReadHeaderTimeout, ReadTimeout, and MaxHeaderBytes retained 0 slow connections and served 20/20 probes.

This report is distinct from the P2P advisories and from my direct-message goroutine report. This finding concerns Klever-owned HTTP REST startup code (cmd/seednode/api and network/api) using Gin Engine.Run without server-level header deadlines. It does not depend on MultiDataInterceptor, Batch.Decompress, libp2p, malformed P2P messages, or direct-message goroutine spawning.

Details

Seednode REST API, latest release v1.7.17:

  • cmd/seednode/api/api.go:17 defines Start(restAPIInterface, marshalizer).
  • cmd/seednode/api/api.go:18 creates ws := gin.Default().
  • cmd/seednode/api/api.go:23 returns ws.Run(restAPIInterface).
  • cmd/seednode/CLI.md:23 documents --rest-api-interface; it says :8080 binds all interfaces and off disables the API.

Node REST API, latest release v1.7.17:

  • network/api/api.go:79 creates ws = gin.Default().
  • network/api/api.go:98 returns ws.Run(kleverFacade.RestAPIInterface()).
  • cmd/node/main.go:147-150 documents the same --rest-api-interface flag and says :8080 binds all interfaces.
  • docker/README.md:56-61 and docker/README.md:67-70 publish host port 8080 for full-node and validator Docker examples.
  • README.md:264-268 documents that the node exposes a REST API for blockchain queries and operations.

The seednode REST API source is byte-identical across v1.7.14 through v1.7.17; the captured runtime PoC was executed on v1.7.17.

Current develop commit 10bcfd50 remains affected:

  • network/api/api.go:98 still returns ws.Run(kleverFacade.RestAPIInterface()).
  • cmd/seednode/api/api.go:59 still returns ws.Run(restAPIInterface).

Gin v1.9.1 implements Engine.Run as:

func (engine *Engine) Run(addr ...string) (err error) {
    address := resolveAddress(addr)
    err = http.ListenAndServe(address, engine.Handler())
    return
}

In my source sweep, I did not find a production http.Server{ReadHeaderTimeout: ...} wrapper for either REST start path. The only ReadHeaderTimeout hit I found in the repository was a test helper under network/api/websocket/routes_test.go.

PoC

GitHub Private Vulnerability Reporting does not appear to allow file attachments in this form, so I am including the reproduction command and captured output inline. I can paste the full 254-line Go test patch in a reply immediately if useful.

The test starts two local child servers:

  1. Vulnerable: the real cmd/seednode/api.Start path.
  2. Fixed control: the same Gin router served through http.Server{ReadHeaderTimeout: 250ms, ReadTimeout: 250ms, MaxHeaderBytes: 4096}.

Reproduction from a clean checkout:

git clone https://github.com/klever-io/klever-go
cd klever-go
git checkout v1.7.17

# Apply the PoC patch to cmd/seednode/api.
# I can provide the full patch in this advisory thread.

go test ./cmd/seednode/api -run TestPoC_SeednodeAPISlowlorisDifferential -count=1 -v -timeout 60s

Captured output on v1.7.17:

POC_RESULT mode=vulnerable slow_connections_opened=120 slow_connections_still_open=111 legitimate_probe_ok=0 legitimate_probe_fail=20
POC_RESULT mode=fixed slow_connections_opened=120 slow_connections_still_open=0 legitimate_probe_ok=20 legitimate_probe_fail=0

The vulnerable server also logs repeated accept failures:

http: Accept error: accept tcp 127.0.0.1:56415: accept: too many open files; retrying in 1s

Impact

For an externally reachable Klever REST listener, a single unauthenticated client can retain many server-side connections by never completing HTTP headers. Because the Go server has no read-header deadline, those connections persist until the client closes them or an external proxy/firewall intervenes.

The direct result is REST API unavailability for legitimate clients. The local proof demonstrates this as 0/20 legitimate /log probes succeeding while the vulnerable server is saturated, versus 20/20 succeeding with the fixed server wrapper.

I am not claiming default public internet exposure. The default bind is localhost:8080. The affected condition is a REST API listener exposed through Klever's documented all-interface bind or Docker port-publish deployment shape.

This maps to the SECURITY.md High category: "Denial of Service affecting network availability." If Klever treats externally reachable REST API unavailability as non-critical because the default bind is localhost, the conservative classification is Medium under "Performance degradation attacks" / "Non-critical DoS vectors."

All testing was local loopback only. I did not contact Klever mainnet, public testnet, hosted RPCs, explorers, or third-party production infrastructure.

Suggested fix:

Start both REST APIs through explicit http.Server values instead of Engine.Run, for example:

srv := &http.Server{
    Addr:              restAPIInterface,
    Handler:           ws.Handler(),
    ReadHeaderTimeout: 5 * time.Second,
    ReadTimeout:       10 * time.Second,
    WriteTimeout:      30 * time.Second,
    IdleTimeout:       120 * time.Second,
    MaxHeaderBytes:    32 << 10,
}
return srv.ListenAndServe()

Apply the same pattern to:

  • cmd/seednode/api.Start
  • network/api.Start

If Klever expects deployments to expose the REST API through a reverse proxy, I still recommend setting server-level limits in the application. That keeps the binary safe when operators use the documented direct bind or Docker port-publish path.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 1.7.17"
      },
      "package": {
        "ecosystem": "Go",
        "name": "github.com/klever-io/klever-go"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.7.14"
            },
            {
              "fixed": "1.7.18"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-400",
      "CWE-770"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-06-05T16:41:58Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "### Summary\n\nThe Klever seednode REST API starts a Gin engine with `Engine.Run(restAPIInterface)`. In Gin v1.9.1, `Engine.Run` calls Go\u0027s default `http.ListenAndServe`, which constructs an HTTP server without application-level `ReadHeaderTimeout`, `ReadTimeout`, or `MaxHeaderBytes` limits.\n\nAn unauthenticated client that can reach a REST listener bound with Klever\u0027s documented `--rest-api-interface :8080` all-interface option can hold incomplete HTTP headers open indefinitely. In a local proof against the real `cmd/seednode/api.Start` path on `v1.7.17`, 120 slow-header connections caused 20/20 legitimate `/log` probes to fail with `accept: too many open files`. A fixed control using the same Gin router behind an explicit `http.Server` with `ReadHeaderTimeout`, `ReadTimeout`, and `MaxHeaderBytes` retained 0 slow connections and served 20/20 probes.\n\nThis report is distinct from the P2P advisories and from my direct-message goroutine report. This finding concerns Klever-owned HTTP REST startup code (`cmd/seednode/api` and `network/api`) using Gin `Engine.Run` without server-level header deadlines. It does not depend on `MultiDataInterceptor`, `Batch.Decompress`, libp2p, malformed P2P messages, or direct-message goroutine spawning.\n\n### Details\n\nSeednode REST API, latest release `v1.7.17`:\n\n- `cmd/seednode/api/api.go:17` defines `Start(restAPIInterface, marshalizer)`.\n- `cmd/seednode/api/api.go:18` creates `ws := gin.Default()`.\n- `cmd/seednode/api/api.go:23` returns `ws.Run(restAPIInterface)`.\n- `cmd/seednode/CLI.md:23` documents `--rest-api-interface`; it says `:8080` binds all interfaces and `off` disables the API.\n\nNode REST API, latest release `v1.7.17`:\n\n- `network/api/api.go:79` creates `ws = gin.Default()`.\n- `network/api/api.go:98` returns `ws.Run(kleverFacade.RestAPIInterface())`.\n- `cmd/node/main.go:147-150` documents the same `--rest-api-interface` flag and says `:8080` binds all interfaces.\n- `docker/README.md:56-61` and `docker/README.md:67-70` publish host port `8080` for full-node and validator Docker examples.\n- `README.md:264-268` documents that the node exposes a REST API for blockchain queries and operations.\n\nThe seednode REST API source is byte-identical across `v1.7.14` through `v1.7.17`; the captured runtime PoC was executed on `v1.7.17`.\n\nCurrent `develop` commit `10bcfd50` remains affected:\n\n- `network/api/api.go:98` still returns `ws.Run(kleverFacade.RestAPIInterface())`.\n- `cmd/seednode/api/api.go:59` still returns `ws.Run(restAPIInterface)`.\n\nGin v1.9.1 implements `Engine.Run` as:\n\n```go\nfunc (engine *Engine) Run(addr ...string) (err error) {\n    address := resolveAddress(addr)\n    err = http.ListenAndServe(address, engine.Handler())\n    return\n}\n```\n\nIn my source sweep, I did not find a production `http.Server{ReadHeaderTimeout: ...}` wrapper for either REST start path. The only `ReadHeaderTimeout` hit I found in the repository was a test helper under `network/api/websocket/routes_test.go`.\n\n### PoC\n\nGitHub Private Vulnerability Reporting does not appear to allow file attachments in this form, so I am including the reproduction command and captured output inline. I can paste the full 254-line Go test patch in a reply immediately if useful.\n\nThe test starts two local child servers:\n\n1. Vulnerable: the real `cmd/seednode/api.Start` path.\n2. Fixed control: the same Gin router served through `http.Server{ReadHeaderTimeout: 250ms, ReadTimeout: 250ms, MaxHeaderBytes: 4096}`.\n\nReproduction from a clean checkout:\n\n```bash\ngit clone https://github.com/klever-io/klever-go\ncd klever-go\ngit checkout v1.7.17\n\n# Apply the PoC patch to cmd/seednode/api.\n# I can provide the full patch in this advisory thread.\n\ngo test ./cmd/seednode/api -run TestPoC_SeednodeAPISlowlorisDifferential -count=1 -v -timeout 60s\n```\n\nCaptured output on `v1.7.17`:\n\n```text\nPOC_RESULT mode=vulnerable slow_connections_opened=120 slow_connections_still_open=111 legitimate_probe_ok=0 legitimate_probe_fail=20\nPOC_RESULT mode=fixed slow_connections_opened=120 slow_connections_still_open=0 legitimate_probe_ok=20 legitimate_probe_fail=0\n```\n\nThe vulnerable server also logs repeated accept failures:\n\n```text\nhttp: Accept error: accept tcp 127.0.0.1:56415: accept: too many open files; retrying in 1s\n```\n\n### Impact\n\nFor an externally reachable Klever REST listener, a single unauthenticated client can retain many server-side connections by never completing HTTP headers. Because the Go server has no read-header deadline, those connections persist until the client closes them or an external proxy/firewall intervenes.\n\nThe direct result is REST API unavailability for legitimate clients. The local proof demonstrates this as 0/20 legitimate `/log` probes succeeding while the vulnerable server is saturated, versus 20/20 succeeding with the fixed server wrapper.\n\nI am not claiming default public internet exposure. The default bind is `localhost:8080`. The affected condition is a REST API listener exposed through Klever\u0027s documented all-interface bind or Docker port-publish deployment shape.\n\nThis maps to the `SECURITY.md` High category: \"Denial of Service affecting network availability.\" If Klever treats externally reachable REST API unavailability as non-critical because the default bind is localhost, the conservative classification is Medium under \"Performance degradation attacks\" / \"Non-critical DoS vectors.\"\n\nAll testing was local loopback only. I did not contact Klever mainnet, public testnet, hosted RPCs, explorers, or third-party production infrastructure.\n\nSuggested fix:\n\nStart both REST APIs through explicit `http.Server` values instead of `Engine.Run`, for example:\n\n```go\nsrv := \u0026http.Server{\n    Addr:              restAPIInterface,\n    Handler:           ws.Handler(),\n    ReadHeaderTimeout: 5 * time.Second,\n    ReadTimeout:       10 * time.Second,\n    WriteTimeout:      30 * time.Second,\n    IdleTimeout:       120 * time.Second,\n    MaxHeaderBytes:    32 \u003c\u003c 10,\n}\nreturn srv.ListenAndServe()\n```\n\nApply the same pattern to:\n\n- `cmd/seednode/api.Start`\n- `network/api.Start`\n\nIf Klever expects deployments to expose the REST API through a reverse proxy, I still recommend setting server-level limits in the application. That keeps the binary safe when operators use the documented direct bind or Docker port-publish path.",
  "id": "GHSA-w4c6-7r69-w7j9",
  "modified": "2026-06-05T16:41:58Z",
  "published": "2026-06-05T16:41:58Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/klever-io/klever-go/security/advisories/GHSA-w4c6-7r69-w7j9"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/klever-io/klever-go"
    },
    {
      "type": "WEB",
      "url": "https://github.com/klever-io/klever-go/releases/tag/v1.7.18"
    }
  ],
  "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": "klever-go: REST API slow-header connection exhaustion via Gin Engine.Run"
}


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…

Detection rules are retrieved from Rulezet.

Loading…

Loading…