TL;DR
While testing OpenSearch 3.2.0, I found an asymmetric Denial of
Service (DoS) condition in how the engine handles query_string
queries.
By crafting Lucene-style inputs that nest boolean operators
and disjunctions, it’s possible to build huge query trees that stay
under OpenSearch’s per-node clause limits but explode the overall
number of nodes. The result is excessive CPU usage and heap pressure
during query parsing, rewriting, and scoring, eventually leading to a
process being killed by the OS or container orchestrator (e.g., exit
code 137).
This issue was assigned CVE-2025-9624 and classified under
CWE-674: Uncontrolled Recursion. It affects OpenSearch where
query_string inputs from potentially untrusted sources can reach the
cluster without an upper bound on complexity. After discussing with the Amazon’s OpenSearch team, the problem was fixed by
introducing a new cluster-wide setting:
search.query.max_query_string_lengthwhich rejects overly long query strings early in the parsing stage.
If you expose query_string to untrusted or semi-trusted clients, you
should:
- Upgrade to OpenSearch 3.3.0 or later.
- Configure a reasonable
search.query.max_query_string_lengthvalue for your environment. - Avoid letting arbitrary users send raw Lucene-style
query_stringinput when a safer DSL or templated queries would do.
Background: OpenSearch, query_string and Clause Limits
OpenSearch is built on top of Apache Lucene and exposes multiple ways to express queries:
- Structured JSON DSL (e.g.,
bool,term,range). - Higher-level helpers like
multi_match. - The
query_stringquery, which lets users write Lucene-style strings such as:
(title:security OR description:"denial of service") AND product:opensearchThe query_string query is convenient, but it’s also dangerous when
users can control it. It has to:
- Parse the string into a query tree.
- Expand field lists (multi-field queries).
- Build Lucene queries like
BooleanQuery,DisjunctionMaxQuery,PhraseQuery, and others.
To prevent runaway queries, OpenSearch already had clause limits:
indices.query.bool.max_clause_countcaps the number of clauses perBooleanQuerynode.- Internally,
IndexSearcher.setMaxClauseCount(...)enforces a limit to avoid pathological boolean expressions.
However, this limit is local to each boolean node. It does NOT cap:
- The total number of nodes in the query tree.
- The fan-out of non-boolean containers, such as
DisjunctionMaxQuery, which are commonly used to aggregate multi-field expansions.
That gap is what made CVE-2025-9624 possible.
The Vulnerable Design
Per-node limits, global problem
At a high level, the vulnerable behavior can be summarized as:
- OpenSearch enforces
indices.query.bool.max_clause_countperBooleanQuery. - The
query_stringparser and related builders can create deep trees of booleans and disjunctions. - Other containers, like
DisjunctionMaxQuery, are not bounded by the boolean clause limit. - There is no global cap on overall query size or node count.
This means an attacker can construct queries where:
- No single boolean node violates
max_clause_count. - But the entire tree still becomes enormous, consuming CPU and
heap during:
- Parsing
- Rewrite phases
- Scoring and relevance calculation
Asymmetric DoS in practice
This is an asymmetric DoS because:
- The attacker’s cost is tiny: a single crafted
_searchrequest with a “large”query_stringvalue with nested boolean/disjunction operations. - The defender’s cost is huge: OpenSearch spends significant CPU and memory processing that query before failing.
Building the Query Bomb
The key observation was that I could grow the query tree combinatorially while keeping each boolean node “small”.
A simplified example of the pattern looks like:
GET _search{ "query": { "query_string": { "query": "winAd AND (rises OR rising) winAd AND (rises OR rising) winAd AND (rises OR rising) ..." } }}By repeating and nesting groups, using multi-field expansions, and keeping the boolean limits intact, the resulting Lucene structure becomes massive.
Reproducing the Issue


Effects observed:
- CPU spikes
- Heavy GC pressure
- Sometimes termination (
137/OOMKilled)
Demo (video)
Direct link: https://youtu.be/Z06POBsayqMImpact Assessment
CVSS v4.0 score: 8.3 (High)
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:HRoot Cause
The issue stems from local limits without global bounds. Boolean node limits do not restrict the total query tree size. Multi-field expansions and disjunctions multiply complexity until the engine collapses under CPU/memory pressure.
This matches CWE-674: Uncontrolled Recursion.
The Fix: search.query.max_query_string_length
OpenSearch PR #19491 added:
search.query.max_query_string_length- Default: 32,000
- Enforced early in
QueryStringQueryParser.parse(...) - Rejects overly long
query_stringvalues before parsing and expansion
Included starting in OpenSearch 3.3.
I want to publicly thanks the Amazon’s OpenSearch team for such a professional triaging and transparency for this vulnerability report.
Defensive Recommendations
- Upgrade to 3.3.0+
- Set a stricter
search.query.max_query_string_length - Avoid exposing raw
query_stringto untrusted clients - Apply rate limiting and monitor heavy queries
Disclosure Timeline
- 2025-09-04 — Issue reported
- 2025-09-17 — Confirmed by OpenSearch team
- 2025-10-01 — Fix merged
- 2025-10-14 — OpenSearch 3.3 released
- 2025-11-25 — Public disclosure (Fluid Attacks + CVE publication)