ISSUE-116 Fix the vulnerability in pluginsChange.php

This commit is contained in:
Brayan Pereyra
2025-09-05 19:30:59 +00:00
parent 125a699323
commit b2f25e3a5f

View File

@@ -9,9 +9,31 @@
use ProcessMaker\Plugins\PluginRegistry; use ProcessMaker\Plugins\PluginRegistry;
/**
* Main execution block - handles plugin change requests and returns JSON response
*/
try {
$result = handlePluginChange();
header('Content-Type: application/json');
echo json_encode($result);
} catch (Exception $e) {
// Handle any unexpected errors with proper HTTP status
header('Content-Type: application/json');
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Invalid request']);
}
/**
* Handles plugin enable/disable operations with security validation
*
* @return array Response array with success status and details
* @throws InvalidArgumentException When input parameters are invalid
* @throws SecurityException When security validation fails
* @throws FileNotFoundException When plugin files are not found
*/
function handlePluginChange() { function handlePluginChange() {
try { try {
// Validar parámetros de entrada // Validate required input parameters
if (!isset($_GET['id']) || !isset($_GET['status'])) { if (!isset($_GET['id']) || !isset($_GET['status'])) {
throw new InvalidArgumentException('Missing required parameters'); throw new InvalidArgumentException('Missing required parameters');
} }
@@ -19,40 +41,44 @@ function handlePluginChange() {
$pluginFile = $_GET['id']; $pluginFile = $_GET['id'];
$pluginStatus = $_GET['status']; $pluginStatus = $_GET['status'];
// Validar formato del archivo de plugin // Validate plugin file format (alphanumeric, underscore, hyphen + .php extension)
if (!preg_match('/^[a-zA-Z0-9_-]+\.php$/', $pluginFile)) { if (!preg_match('/^[a-zA-Z0-9_-]+\.php$/', $pluginFile)) {
throw new InvalidArgumentException('Invalid plugin file format'); throw new InvalidArgumentException('Invalid plugin file format');
} }
// Validar status // Validate status parameter (only '0' or '1' allowed)
if (!in_array($pluginStatus, ['0', '1'], true)) { if (!in_array($pluginStatus, ['0', '1'], true)) {
throw new InvalidArgumentException('Invalid plugin status'); throw new InvalidArgumentException('Invalid plugin status');
} }
// Sanitizar nombre del plugin // Sanitize plugin name by removing directory components and extension
$pluginName = basename(str_replace(".php", "", $pluginFile)); $pluginName = basename(str_replace(".php", "", $pluginFile));
// Additional validation for plugin name format
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $pluginName)) { if (!preg_match('/^[a-zA-Z0-9_-]+$/', $pluginName)) {
throw new InvalidArgumentException('Invalid plugin name format'); throw new InvalidArgumentException('Invalid plugin name format');
} }
// Validar rutas de forma segura // Secure path validation to prevent directory traversal attacks
$pluginFilePath = realpath(PATH_PLUGINS . $pluginFile); $pluginFilePath = realpath(PATH_PLUGINS . $pluginFile);
$pluginDirPath = realpath(PATH_PLUGINS . $pluginName); $pluginDirPath = realpath(PATH_PLUGINS . $pluginName);
$pluginsDir = realpath(PATH_PLUGINS); $pluginsDir = realpath(PATH_PLUGINS);
// Ensure plugin file is within the allowed plugins directory
if (!$pluginFilePath || strpos($pluginFilePath, $pluginsDir) !== 0) { if (!$pluginFilePath || strpos($pluginFilePath, $pluginsDir) !== 0) {
throw new SecurityException('Plugin file outside allowed directory'); throw new SecurityException('Plugin file outside allowed directory');
} }
// Verify plugin file exists
if (!is_file($pluginFilePath)) { if (!is_file($pluginFilePath)) {
throw new FileNotFoundException('Plugin file not found'); throw new FileNotFoundException('Plugin file not found');
} }
// Get plugin registry singleton instance
$oPluginRegistry = PluginRegistry::loadSingleton(); $oPluginRegistry = PluginRegistry::loadSingleton();
if ($pluginStatus === '1') { if ($pluginStatus === '1') {
// Deshabilitar plugin // Disable plugin operation
$details = $oPluginRegistry->getPluginDetails($pluginFile); $details = $oPluginRegistry->getPluginDetails($pluginFile);
if ($details) { if ($details) {
$oPluginRegistry->disablePlugin($details->getNamespace()); $oPluginRegistry->disablePlugin($details->getNamespace());
@@ -61,13 +87,14 @@ function handlePluginChange() {
return ['success' => true, 'action' => 'disabled', 'plugin' => $details->getNamespace()]; return ['success' => true, 'action' => 'disabled', 'plugin' => $details->getNamespace()];
} }
} else { } else {
// Habilitar plugin // Enable plugin operation
if ($pluginDirPath && is_dir($pluginDirPath)) { if ($pluginDirPath && is_dir($pluginDirPath)) {
// Safely include the validated plugin file
require_once($pluginFilePath); require_once($pluginFilePath);
$details = $oPluginRegistry->getPluginDetails($pluginFile); $details = $oPluginRegistry->getPluginDetails($pluginFile);
if ($details) { if ($details) {
$oPluginRegistry->enablePlugin($details->getNamespace()); $oPluginRegistry->enablePlugin($details->getNamespace());
$oPluginRegistry->setupPlugins(); $oPluginRegistry->setupPlugins(); // Initialize all enabled plugins
$oPluginRegistry->savePlugin($details->getNamespace()); $oPluginRegistry->savePlugin($details->getNamespace());
G::auditLog("EnablePlugin", "Plugin Name: " . $details->getNamespace()); G::auditLog("EnablePlugin", "Plugin Name: " . $details->getNamespace());
return ['success' => true, 'action' => 'enabled', 'plugin' => $details->getNamespace()]; return ['success' => true, 'action' => 'enabled', 'plugin' => $details->getNamespace()];
@@ -77,21 +104,12 @@ function handlePluginChange() {
} }
} }
// Fallback response if no operation was performed
return ['success' => false, 'error' => 'Plugin operation failed']; return ['success' => false, 'error' => 'Plugin operation failed'];
} catch (Exception $e) { } catch (Exception $e) {
// Log error and return failure response
G::auditLog('PluginChange', 'Error: ' . $e->getMessage()); G::auditLog('PluginChange', 'Error: ' . $e->getMessage());
return ['success' => false, 'error' => $e->getMessage()]; return ['success' => false, 'error' => $e->getMessage()];
} }
} }
// Ejecutar y devolver respuesta JSON
try {
$result = handlePluginChange();
header('Content-Type: application/json');
echo json_encode($result);
} catch (Exception $e) {
header('Content-Type: application/json');
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Invalid request']);
}