# [CRIT] [GHSA / CRITICAL] CVE-2026-54003: Kirby: External Initialization of the Panel on reverse proxy setups with the `Forwarded` header

**Source:** GitHub Security Advisories
**Published:** 2026-06-18
**Article:** https://github.com/advisories/GHSA-whxw-24jc-cwmv

## Threat Profile

Kirby: External Initialization of the Panel on reverse proxy setups with the `Forwarded` header

### TL;DR

This vulnerability affects Kirby sites that have no configured user accounts and are running on publicly accessible servers behind a reverse proxy that sets the `Forwarded: for=...`, `X-Client-IP`, or `X-Real-IP` request header.

It was possible to install the Panel (= create the first admin user) in these setups even from remote IP addresses.

**This vulnerability is of critical severity …

## Indicators of Compromise (high-fidelity only)

- **CVE:** `CVE-2026-54003`

## MITRE ATT&CK Techniques

- **T1190** — Exploit Public-Facing Application
- **T1656** — Impersonation
- **T1136.001** — Create Account: Local Account
- **T1078.003** — Valid Accounts: Local Accounts

## Kill chain phases observed

_(none detected from narrative keywords)_

## Recommended hunts

### Kirby CMS Panel installation endpoint accessed from external IP (CVE-2026-54003)

`UC_10_1` · phase: **exploit** · confidence: **Medium** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats summariesonly=t count min(_time) as firstTime max(_time) as lastTime values(Web.http_method) as methods values(Web.status) as statuses values(Web.http_user_agent) as user_agents from datamodel=Web.Web where (Web.url="*/panel/installation*" OR Web.url="*/panel/install*" OR Web.url="*/api/users*") Web.http_method IN ("POST","PUT","GET") by Web.src, Web.dest, Web.url, Web.site | `drop_dm_object_name(Web)` | where NOT cidrmatch("10.0.0.0/8", src) AND NOT cidrmatch("172.16.0.0/12", src) AND NOT cidrmatch("192.168.0.0/16", src) AND NOT cidrmatch("127.0.0.0/8", src) | `security_content_ctime(firstTime)` | sort - firstTime
```

**Defender KQL:**
```kql
// Defender Advanced Hunting has no native web-access-log table; this surfaces the post-install admin login from the host side as a proxy signal
let PanelHosts = DeviceInfo
    | where Timestamp > ago(7d)
    | where IsInternetFacing == true
    | distinct DeviceId, DeviceName;
DeviceNetworkEvents
| where Timestamp > ago(7d)
| where DeviceId in (PanelHosts)
| where ActionType == "InboundConnectionAccepted"
| where LocalPort in (80, 443, 8080, 8443)
| where RemoteIPType == "Public"
| where InitiatingProcessFileName in~ ("nginx","nginx.exe","httpd","httpd.exe","apache2","caddy","w3wp.exe","php-fpm","php-fpm.exe")
| project Timestamp, DeviceName, RemoteIP, RemotePort, LocalPort, InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc
```

### Kirby CMS admin account file created under site/accounts/ by web server process (CVE-2026-54003)

`UC_10_2` · phase: **install** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats summariesonly=t count min(_time) as firstTime max(_time) as lastTime values(Filesystem.file_name) as files values(Filesystem.process_name) as procs from datamodel=Endpoint.Filesystem where (Filesystem.file_path="*/site/accounts/*" OR Filesystem.file_path="*\\site\\accounts\\*") Filesystem.action="created" Filesystem.process_name IN ("php-fpm","php-fpm7*","php-fpm8*","php.exe","php-cgi.exe","nginx","nginx.exe","httpd","apache2","w3wp.exe","caddy") by Filesystem.dest, Filesystem.file_path, Filesystem.user | `drop_dm_object_name(Filesystem)` | `security_content_ctime(firstTime)` | sort - firstTime
```

**Defender KQL:**
```kql
DeviceFileEvents
| where Timestamp > ago(7d)
| where ActionType in ("FileCreated","FileRenamed")
| where FolderPath has_any ("/site/accounts/", @"\site\accounts\")
| where FileName in~ ("index.php","user.txt","index.yml","index.yaml",".htpasswd") or FileName endswith ".php"
| where InitiatingProcessFileName in~ ("php-fpm","php-fpm7.4","php-fpm8.0","php-fpm8.1","php-fpm8.2","php-fpm8.3","php.exe","php-cgi.exe","php","nginx","nginx.exe","httpd","httpd.exe","apache2","w3wp.exe","caddy")
| project Timestamp, DeviceName, FolderPath, FileName, SHA256, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessCommandLine, InitiatingProcessAccountName
| order by Timestamp desc
```

### IOC-driven hunts (use shared templates)

These are standard IOC-substitution hunts — the canonical SPL and KQL live once in [`_TEMPLATES.md`](../_TEMPLATES.md), so we don't repeat the same boilerplate on every CVE / hash / network-IOC briefing.

- **Asset exposure — vulnerability matches article CVE(s)** ([template](../_TEMPLATES.md#asset-exposure)) — phase: **recon**, confidence: **High**
  - CVE(s): `CVE-2026-54003`


## Why this matters

Severity classified as **CRIT** based on: CVE present, 3 use case(s) fired, 4 technique(s) inferred. Read the full article for actor attribution, tooling details, and any defanged IOCs in the body that aren't visible in the RSS summary.
