Summary
check_authorization validates privileges against the ?db= query parameter, but execution resolves the target database from the statement's ON <db> clause. A non-admin user with write access to any single database can execute destructive operations on databases they have no access to.
Findings covered: #2 (root cause), #6 (bind parameter injection that compounds this)
Root Cause
In application/query_service.rs, check_authorization (lines 185-238) always validates against the db parameter (the ?db= query param from the HTTP request). But execution resolves the effective target from the statement itself:
let target_db = if rp_db.is_empty() { db } else { rp_db.as_str() };
The ON <db> clause in the statement takes precedence over the ?db= param at execution time, but authorization never sees it.
Affected Statements
| Statement |
Auth checks |
Execution uses |
ALTER RETENTION POLICY ... ON victim |
?db= |
ON victim |
CREATE RETENTION POLICY ... ON victim |
?db= |
ON victim |
DROP RETENTION POLICY ... ON victim |
?db= |
ON victim |
CREATE CONTINUOUS QUERY ... ON victim |
?db= |
ON victim |
DROP CONTINUOUS QUERY ... ON victim |
?db= |
ON victim |
DROP SERIES ... ON victim |
?db= |
ON victim |
CREATE MATERIALIZED VIEW ... ON victim |
?db= |
ON victim |
DROP MATERIALIZED VIEW ... ON victim |
?db= |
ON victim |
SHOW RETENTION POLICIES ON victim |
?db= |
ON victim |
SHOW MEASUREMENTS ON victim |
?db= |
ON victim |
SHOW TAG KEYS/VALUES ON victim |
?db= |
ON victim |
SHOW FIELD KEYS ON victim |
?db= |
ON victim |
SHOW SERIES ON victim |
?db= |
ON victim |
Not affected: DELETE, DROP MEASUREMENT — these use the request ?db= for execution.
Impact
- Data destruction:
ALTER RETENTION POLICY autogen ON victim DURATION 1h forces deletion of another tenant's data.
- Schema poisoning:
CREATE CONTINUOUS QUERY ... ON victim points CQs at another tenant's database.
- Information disclosure:
SHOW MEASUREMENTS ON victim leaks schema/tag values.
- Data manipulation:
DROP SERIES ... ON victim deletes another tenant's series.
Reproduction
# As a non-admin user with write on "mine":
POST /query?db=mine&q=ALTER%20RETENTION%20POLICY%20autogen%20ON%20victim%20DURATION%201h%20REPLICATION%201
# Returns 200 — authorization passed on "mine", but victim's RP is now 1h
#6 — Bind Parameter Injection (compounds #2)
substitute_bind_params in adapters/http/query.rs (lines 75-92):
- Backslash not escaped: Only
' -> '\' escaping. A value ending in \ escapes the closing quote: a\ -> 'a\' -> unterminated string.
- Naive replacement:
String::replace does substring matching — $host matches inside $hostname.
- Order-dependent: HashMap iteration order means overlapping param names produce different results.
Downstream ClickHouse SQL generation is properly escaped (no raw SQL injection), but this is InfluxQL-level injection that compounds the cross-DB bypass in #2.
Recommended Fix
check_authorization must resolve the effective target DB from the statement (the same resolution execution uses) and authorize against that.
- For bind params: escape both
\ and ', match $name on word boundaries, or bind into the AST post-parse.
- Add integration tests asserting 403 for cross-DB statements.
Confidence
High. Traced authz -> execution for every affected statement type.
Summary
check_authorizationvalidates privileges against the?db=query parameter, but execution resolves the target database from the statement'sON <db>clause. A non-admin user with write access to any single database can execute destructive operations on databases they have no access to.Findings covered: #2 (root cause), #6 (bind parameter injection that compounds this)
Root Cause
In
application/query_service.rs,check_authorization(lines 185-238) always validates against thedbparameter (the?db=query param from the HTTP request). But execution resolves the effective target from the statement itself:The
ON <db>clause in the statement takes precedence over the?db=param at execution time, but authorization never sees it.Affected Statements
ALTER RETENTION POLICY ... ON victim?db=ON victimCREATE RETENTION POLICY ... ON victim?db=ON victimDROP RETENTION POLICY ... ON victim?db=ON victimCREATE CONTINUOUS QUERY ... ON victim?db=ON victimDROP CONTINUOUS QUERY ... ON victim?db=ON victimDROP SERIES ... ON victim?db=ON victimCREATE MATERIALIZED VIEW ... ON victim?db=ON victimDROP MATERIALIZED VIEW ... ON victim?db=ON victimSHOW RETENTION POLICIES ON victim?db=ON victimSHOW MEASUREMENTS ON victim?db=ON victimSHOW TAG KEYS/VALUES ON victim?db=ON victimSHOW FIELD KEYS ON victim?db=ON victimSHOW SERIES ON victim?db=ON victimNot affected:
DELETE,DROP MEASUREMENT— these use the request?db=for execution.Impact
ALTER RETENTION POLICY autogen ON victim DURATION 1hforces deletion of another tenant's data.CREATE CONTINUOUS QUERY ... ON victimpoints CQs at another tenant's database.SHOW MEASUREMENTS ON victimleaks schema/tag values.DROP SERIES ... ON victimdeletes another tenant's series.Reproduction
#6 — Bind Parameter Injection (compounds #2)
substitute_bind_paramsinadapters/http/query.rs(lines 75-92):'->'\'escaping. A value ending in\escapes the closing quote:a\->'a\'-> unterminated string.String::replacedoes substring matching —$hostmatches inside$hostname.Downstream ClickHouse SQL generation is properly escaped (no raw SQL injection), but this is InfluxQL-level injection that compounds the cross-DB bypass in #2.
Recommended Fix
check_authorizationmust resolve the effective target DB from the statement (the same resolution execution uses) and authorize against that.\and', match$nameon word boundaries, or bind into the AST post-parse.Confidence
High. Traced authz -> execution for every affected statement type.