← Back to Research
Research · July 22, 2026

How a wide-range eth_getLogs query turns a few hundred bytes into an unbounded response on exposed go-ethereum RPC nodes

Simon Morley·5 min read

eth_getLogs returns every log matching a {fromBlock, toBlock, address, topics} filter across the requested block span, serialised into a single JSON-RPC response — so a few hundred bytes of request expands into an arbitrarily large reply bounded only by how many logs match, a response-amplification denial-of-service payable by an unauthenticated caller when go-ethereum's JSON-RPC transport is bound to a routable interface.

Why this matters

Anyone running a go-ethereum node with the HTTP or WebSocket JSON-RPC exposed — public and semi-public RPC providers, indexers, and self-hosted endpoints — serves eth_getLogs as an unauthenticated method. It is a documented expensive read, and go-ethereum ships a control (--rpc.rangelimit) for it, but that control bounds the wrong dimension. It caps the block span an attacker may request; it does not cap the number or byte-size of logs a permitted span returns. A naïve fix — "just set a range limit" — therefore does not close the amplification: a single query that stays inside the configured range but selects a high-volume topic (for example a busy ERC-20 Transfer event) still forces the node to scan the range and emit the whole matched-log set. The cost lands as egress bandwidth plus serialisation CPU on the node, per request, from an unauthenticated source.

How the attack works

  • Entry point: the JSON-RPC eth_getLogs method on the HTTP (--http) or WebSocket (--ws) transport. go-ethereum applies no per-method authorization on these transports; reachability is purely a function of the bind address and the enabled namespaces.
  • Preconditions: the log-filter RPC is reachable on a non-loopback address, with --rpc.rangelimit unset or set loose and no result-size/result-count cap at the RPC edge. The loopback default is not remotely reachable.
  • Processing path: eth_getLogs is served by FilterAPI.GetLogs (eth/filters/api.go), which builds a Filter and calls filter.Logs(ctx) (eth/filters/filter.go). Logs resolves the [fromBlock, toBlock] range and walks it via an indexed and an unindexed path; for every block whose log bloom matches, it decodes the receipts and appends every matching *types.Log into a single result slice, which is returned whole and JSON-serialised back to the caller.
  • Boundary crossed: availability. The controls that exist bound the inputs (--rpc.rangelimit rejects an over-cap span with a -32000/-32602-class error) but not the output — there is no default cap on the number or byte-size of the returned logs.
  • Resulting behaviour: response size scales with the matched-log count, which the attacker steers through range breadth and topic selectivity, entirely within whatever range limit is configured. A tiny request yields a megabytes-class response and the scan/serialise work behind it.

Affected systems

  • Severity: MEDIUM — availability-only (no funds, no consensus break, no authentication bypass).
  • Chain: Ethereum.
  • Implementation: go-ethereum (ethereum/go-ethereum), run with the log-filter RPC exposed and no — or a loose — --rpc.rangelimit, and without a result-size/result-count cap at the RPC edge.
  • Component / method: JSON-RPC eth_getLogs (the eth namespace over --http/--ws).
  • Not affected: a loopback-bound RPC (the go-ethereum default), or a node behind a gateway that enforces both a tight block range and a result-size/count cap. This advisory does not establish a specific affected version range beyond "go-ethereum with the described exposure"; the behaviour is the documented method semantics, not a version-specific regression.

Evidence

  • Publicly documented: go-ethereum documents eth_getLogs as an expensive read and ships --rpc.rangelimit to cap the block span — the mechanism is a property of the documented method, cited to eth/filters/api.goeth/filters/filter.go in the advisory.
  • Independently reproduced by NullRabbit (lab): the published corpus reproducer (primitive eth_getlogs_response_amp, family response_amp, source_class: original) is shipped in the public dataset NullRabbit/nr-bundles-public. It captures the attack traffic — a wide-range/broad-topic eth_getLogs request that returns a large log set — contrasted with a narrow, selective benign query to the same method against a local self-owned node.
  • Measured: not established as a single constant. The amplification is structural: response bytes scale linearly with the number of matching logs, and the attacker controls that count through range breadth and topic selectivity, so the realised factor depends on the target's chain data and its configured range limit rather than a fixed multiplier. The advisory stands on the source trace and go-ethereum's own documentation, not on a measured amplification factor.

Operator actions

  • Cap the block range with a tight --rpc.rangelimit, and additionally cap the result size/count at the RPC edge (a reverse proxy or RPC gateway) — the range limit alone does not bound a single wide-topic response.
  • Rate-limit eth_getLogs per source, and require pagination for historical scans.
  • Do not expose the log-filter RPC unauthenticated on a routable interface. Keep --http/--ws on loopback or behind an authenticating gateway, and expose only the methods an application needs.
  • Exposure check: confirm whether your --http/--ws bind address is routable, whether --rpc.rangelimit is set and tight, and whether a result-size cap exists at the edge. Detection ideas (response-size and per-source eth_getLogs rate monitoring at the gateway) are suggested controls, not a confirmed vendor fix.

Canonical references

Related attacks

eth_getlogs_response_amp is one instance of NRDAX-T0329 (Unbounded RPC Response Amplification), a chain-agnostic technique class the registry records across multiple decentralised-infrastructure RPC surfaces — the shared mechanism being an unpaginated read whose response scales with an attacker-steered match set. Related links here are limited to that shared technique id and the response_amp family; no further relationship is asserted beyond what the registry records.

Track attacks affecting go-ethereum and Ethereum RPC infrastructure.

Analysis plus reproducer. No weaponised proof-of-concept code.

researchethereumgo-ethereumeth_getlogsresponse_amprpcinfrastructure-security
Publication policy:Why we publish these findings →

Related Posts

One prior-knowledge h2c connection multiplexes N eth_getLogs past an L4 per-connection cap on go-ethereum's JSON-RPC port

·7 min read

go-ethereum's JSON-RPC port terminates cleartext HTTP/2 (h2c) by prior knowledge, so one TCP connection can multiplex N eth_getLogs streams past an L4 edge that only caps connections per IP (nginx stream limit_conn) — measured 20/20 streams and ~39 MB pulled through a single connection the edge counts as one, an amplification-multiplexing bypass of a connection cap, not of an L7 request rate limit.

researchethereumgo-ethereum+5 more

Bypassing go-ethereum's --http.vhosts host allowlist with an empty Host, forged by an HAProxy HTTP/2→1.1 downgrade

·7 min read

An HTTP/2 request with an empty :authority, downgraded to HTTP/1.1 by an HAProxy front, reaches go-ethereum with an empty Host header that its --http.vhosts allowlist accepts (200) though it rejects any concrete disallowed host (403) — a bypass of geth's anti-DNS-rebinding Host filter by an attacker with raw-HTTP/2 reach to the edge.

researchethereumgo-ethereum+5 more