
Cloud Vulnerability DB
A community-led vulnerabilities database
Any user who can chat with the bot (any member of a Slack/Rocket.Chat/etc. workspace the bot sits in — the command has no permission check) can send:
hubot graf db <uid> apiEndpoint=../../../api/admin/users#The apiEndpoint token is accepted by the dashboard-request parser because it matches a key of the internal GrafanaDashboardQuery object. That key is otherwise set only from the operator's HUBOT_GRAFANA_API_ENDPOINT env var (d/d-solo). The value is never validated and is interpolated verbatim into the URL path of the rendered-image URL:
const url = new URL(`${this.host}/render/${query.apiEndpoint}/${uid}/`);new URL() normalizes ../ segments, so the attacker escapes /render/ and controls the whole path of a GET the bot then issues with the bot's Grafana API key attached (Authorization: Bearer …). The fetched body comes back to the chat as an "image" upload (a Slack file or public-read S3 object), so its content reaches the attacker. A trailing # in the injected value pushes the required /<uid>/ into the fragment, so the path ends up exactly as the attacker wrote it.
hubot-grafana (npm)CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N, assuming an admin-scoped API key (a setup the plugin documents). With a Viewer-role key the same bug reads only Viewer-visible endpoints, and that floor is CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N = 6.5 MEDIUM.| # | Location | What happens |
|---|---|---|
| 1 | src/grafana.js (robot.respond(/(?:grafana|graph|graf) (?:dash|dashboard|db) …/i)) | Attacker's chat message; the (.*) remainder (e.g. apiEndpoint=../../../api/admin/users#) is passed to bot.sendDashboardChartFromString. No permission check on this handler. |
| 2 | src/Bot.js | service.parseToGrafanaDashboardRequest(requestString) |
| 3 | src/service/GrafanaService.js | Remainder split on spaces; each key=value part is split into partName/partValue. Tainted partName = "apiEndpoint", partValue = "../../../api/admin/users#". |
| 4 | src/service/GrafanaService.js | if (partName in request.query) { request.query[partName] = partValue; } — apiEndpoint is an own property of GrafanaDashboardQuery (default from HUBOT_GRAFANA_API_ENDPOINT, intended operator-only), so the check passes and the attacker's string overwrites the operator setting with zero validation. |
| 5 | src/service/GrafanaService.js | getDashboard(uid) succeeds for any real dashboard UID, then per panel createImageUrl(req.query, …) runs with the tainted apiEndpoint. |
| 6 | src/grafana-client.js (createImageUrl) | Sink (URL): new URL(${this.host}/render/${query.apiEndpoint}/${uid}/). WHATWG URL normalizes the .. segments; with a trailing # the /<uid>/ becomes the fragment and the path is exactly /api/admin/users. Other parameters go through URLSearchParams (encoded) — only apiEndpoint lands in the path. |
| 7 | src/Bot.js → src/grafana-client.js (download) | When an upload path exists (S3 bucket, or a Slack/Rocket.Chat/Telegram adapter — default-on for Slack), fetch(url, { headers: … Authorization: Bearer <HUBOT_GRAFANA_API_KEY> }); the response body is returned as a Buffer. |
| 8 | src/Bot.js → the configured uploader | The fetched bytes are uploaded into the chat room — the S3 variant creates a public-read object and posts its URL — delivering the arbitrary-endpoint response to the attacker. |
A chat user with no Grafana credentials crosses the bot's service-credential boundary: they point an authenticated GET at any path on the Grafana origin using the bot's bearer token, and get the raw response back in chat. With an admin-scoped key this exposes Grafana admin API data (settings, user lists, org/service-account information); the bot becomes a read proxy for the Grafana instance, and because the path escapes /render/ completely, for any same-origin route behind the Grafana host.
Fixed in 7.4.0. Chat input may now set only an explicit allowlist of query keys (width, height, tz, kiosk). Operator-only keys — including apiEndpoint and orgId — are no longer accepted from chat and can be set solely via their HUBOT_GRAFANA_* environment variables.
There is no configuration toggle to disable the behavior in affected versions, so upgrading to 7.4.0 is the fix. To limit impact — before and after upgrading — run the bot with a least-privilege Grafana credential: a Viewer-role API key or service-account token is sufficient to render dashboards and reduces this issue (and this class of issue generally) to read access over only Viewer-visible endpoints. Avoid configuring the bot with an Admin-scoped key.
Source: NVD
Free Vulnerability Assessment
Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.
Get a personalized demo
"Best User Experience I have ever seen, provides full visibility to cloud workloads."
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
"We know that if Wiz identifies something as critical, it actually is."