662 words
3 minutes
CVE-2025-9624: Nested Boolean/Disjunction Asymmetric DoS in Amazon's OpenSearch query_string - Chick

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_length

which 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_length value for your environment.
  • Avoid letting arbitrary users send raw Lucene-style query_string input 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_string query, which lets users write Lucene-style strings such as:
(title:security OR description:"denial of service") AND product:opensearch

The query_string query is convenient, but it’s also dangerous when users can control it. It has to:

  1. Parse the string into a query tree.
  2. Expand field lists (multi-field queries).
  3. Build Lucene queries like BooleanQuery, DisjunctionMaxQuery, PhraseQuery, and others.

To prevent runaway queries, OpenSearch already had clause limits:

  • indices.query.bool.max_clause_count caps the number of clauses per BooleanQuery node.
  • 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_count per BooleanQuery.
  • The query_string parser 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 _search request with a “large” query_string value 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/Z06POBsayqM

Impact 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:H

Root 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_string values 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#

  1. Upgrade to 3.3.0+
  2. Set a stricter search.query.max_query_string_length
  3. Avoid exposing raw query_string to untrusted clients
  4. 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)

References#

CVE-2025-9624: Nested Boolean/Disjunction Asymmetric DoS in Amazon's OpenSearch query_string - Chick
https://caverav.cl/posts/opensearch-dos/opensearch-dos/
Author
Camilo Vera
Published at
2025-11-25
License
CC BY-NC-SA 4.0