The v5 query builder embeds user-supplied telemetry field-key names from /api/v5/query_range into generated ClickHouse SQL with no escaping or character validation. A field key containing a back-tick or a single quote breaks out of the quoted identifier or the map-accessor string literal and injects SQL that runs with signoz's ClickHouse credentials. Any authenticated principal holding the lowest role (Viewer) can reach it.
The endpoint ( pkg/apiserver/signozapiserver/querier.go , QueryRangeV5 ) is gated by CheckResources(..., Viewer, Editor, Admin) , so any authenticated user reaches the builder. The request body carries structured TelemetryFieldKey objects in groupBy[].name , order[].key.name and selectFields[].name , and their Name reaches the builder verbatim.
The validation gap is that TelemetryFieldKey.Normalize() ( pkg/types/telemetrytypes/field.go:148 ) only splits the name on . / : to parse a data type and does no character validation, and QueryRangeRequest.Validate() rejects only empty names. The ANTLR FilterQuery.g4 lexer that forbids back-ticks and quotes applies only to the -bar filter expression, not to these structured keys, so the character-set restriction that protects the filter path does not cover this one.
There are two sink families. In the identifier context, GroupByKeys ( pkg/querybuilder/fallback_expr.go:19 ) builds "`"+key.Name+"`" and the per-signal statement builders alias and ORDER BY on the same name, so a back-tick in the name closes the quoted identifier. In the string-literal context, the field mappers build map accessors such as mapContains(%s, '%s') ( pkg/telemetrymetadata/field_mapper.go:67 ), %s['%s'] ( :85 ) and %s AS `%s` ( :133 ) with raw key.Name , so a single quote closes the map-key literal; the same shape is present in the logs, traces and metrics field mappers and statement builders. The single-quote sink executes as well, but a group-by name lands in several contexts at once, so a quote that breaks the map accessor makes the query fail closed to a masked 500; the reporters exploited it out-of-band through url() egress and error-based reads.
The mapped field expression is a column operand, not a bound parameter, so go-sqlbuilder emits it directly, and the sqlbuilder.Escape helper used on values does not escape single quotes. Setting fieldContext:"attribute" makes the mapper resolve attributes_string[' '] for any attacker-chosen name, so no telemetry-metadata entry is required to reach the sink.
Verified on a SigNoz v0.141.0. Seed two services so the traces group-by has data to scan:
Obtain a Viewer session token, then POST each request body below to /api/v5/query_range ; the control and each injection differ only in groupBy[0].name , and the injected names run benign ClickHouse SQL ( version() and a system.columns row count) reflected back in the response:
Control request (benign group-by name):
Case A: identifier (back-tick) sink, reflected in-band. The name closes the `...` identifier and adds an attacker column, so the injected version() value comes back in the response.
Injection response (note the extra injected_marker column, value 25.12.5.44 , the ClickHouse version):
Case B: reading a table outside the telemetry scope, in-band. The same break-out injects a scalar subquery against system.columns ; its row count comes back as the injected_system_columns column, and rowsScanned rising from 225 to 5306 confirms the subquery ran server-side.
Injection response (the injected_system_columns column holds 5081 , the system.columns row count):
This is authenticated ClickHouse SQL injection (CWE-89) reachable from the lowest role, Viewer. The injected SQL runs with signoz's ClickHouse credentials, so a caller might read tables outside the intended telemetry scope (including system.* and other data that user can reach), invoke table functions such as url() for server-side requests and file() for local file reads, and run expensive queries for denial of service. The identifier sink returns injected results in-band, as Cases A and B show, including reads of system.* outside the telemetry scope; the string-literal sink executes too but fails closed to a masked 500 on this endpoint, so it is exploited out-of-band.
Upgrade to v0.142.0 or later, which routes every user-controlled field-key name through the ClickHouse quoting helpers so back-ticks and single quotes are escaped instead of interpolated.
Upgrade to v0.142.0 , or restrict signoz's ClickHouse user to least privilege with the url , file , remote and s3 table functions disabled; there is no configuration flag that disables /api/v5/query_range .
Fixed in v0.142.0 by 8e00c04056 ( #12593 ) and 9c886be120 ( #12802 ).
Independently reported, in order of report date:
@tonghuaroot (back-tick identifier break-out; the ANTLR-vs-JSON asymmetry)
@dodge1218 (map-accessor sink; demonstrated url() egress and error-oracle reads)
@456789TZ (both quote and back-tick contexts, and /api/v5/query_range/preview )
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
