Chartbrew: Blind NoSQL Injection via Unescaped Date Variable in Shared Charts
Chartbrew inserts date variables into MongoDB queries without escaping. Via the public embed route for shared charts, an anonymous attacker injects filter conditions and extracts arbitrary fields from the queried collection character by character, including password hashes.
Advisory ID: TP-2026-057
Product: Chartbrew (open-source business-intelligence platform for data visualization, self-hosted)
Vulnerability type: Improper Neutralization of Special Elements in Data Query Logic / NoSQL Injection (CWE-943)
CVE: CVE-2026-85726
CVSS 3.1: 7.5 (High) · CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Vendor advisory: GHSA-mr45-2r9x-cgwf
Affected versions: <= 5.2.2
Fixed in: not yet released (fix on master)
Reported: 16 July 2026
Summary
Chartbrew is a self-hosted open-source business-intelligence platform for data visualization whose shared charts are reachable anonymously via the tokenless embed route GET /chart/:share_string/embedded. The function applyMongoVariables inserts chart query variables of type date via String(runtimeValue) with no escaping into MongoDB query strings (server/sources/plugins/mongodb/mongodb.protocol.js:143-146), while the string, number and boolean types escape or coerce. The post-substitution validator validateMongoQuery (server/modules/validateMongoQuery.js) permits MongoDB operators ($regex/$ne/$gt) as string keys and a // line comment, so an attacker breaks out of the date literal and splices filter conditions into the same find() call. The embed route forwards every non-special query parameter as a runtime variable, unauthenticated. Result: an anonymous blind boolean oracle extracts unprojected fields (e.g. password hashes) from the collection the shared chart queries.
Root cause
For a variable binding of type date inside a quoted placeholder, applyMongoVariables inserts String(runtimeValue) with no escaping (server/sources/plugins/mongodb/mongodb.protocol.js:143-146), unlike the string branch which escapes quotes. The post-substitution validator validateMongoQuery requires one db.collection().method() call chain with literal arguments but permits MongoDB operators as string keys and permits // comments (server/modules/validateMongoQuery.js). An attacker-supplied date value breaks out of the string literal, adds $regex or $ne conditions to the same find() argument, and comments out the template tail. ChartController.findByShareString gates only on chart.public || chart.shareable with user=null and forwards every non-special query parameter as a variable (server/controllers/ChartController.js:1135-1188,1259), reached anonymously at GET /chart/:share_string/embedded (server/api/ChartRoute.js:754). The chart's returned row count is a boolean oracle, enabling char-by-char extraction of any field in the collection.
Proof of Concept
# Inject a $regex condition via the date variable d:
GET /chart/<share_string>/embedded?d=2020-01-01", "password": {"$regex":"^aSECRET"} })//
HTTP/1.1 200 OK
labels ["public1"], data [1] (match: TRUE)
GET /chart/<share_string>/embedded?d=2020-01-01", "password": {"$regex":"^z"} })//
HTTP/1.1 200 OK
labels ["No data"], data [0] (no match: FALSE)
# Without a known date, neutralize the pinned field:
GET /chart/<share_string>/embedded?d=ZZZ", "createdAt": {"$ne": null}, "password": {"$regex":"^a"} })//
HTTP/1.1 200 OK
labels ["public1"], data [1]
Impact
- Anonymous blind read of any field in the collection a shared chart queries, including secrets the chart never displays.
- Read-only, single collection per request.
- No account and no known field value required.
References
Is Something Like This in Your Software?
Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.
