Skip to content

Commit 50dab8e

Browse files
committed
fix(security): patch auth bypass, XSS, RCE, path traversal, arbitrary file write
- Validate JWT signature in SecurityPlugin.isAuthenticated() instead of accepting any Bearer header (CVE: unauthenticated admin access) - Sanitize iconClass to CSS-safe characters and escape with htmlspecialchars() in sidebar rendering (stored XSS via module settings) - Add ALLOWED_WRITE_DIRECTORIES whitelist in FilesManagementProcessor with pre-write path traversal rejection and directory confinement - Sanitize firmware version parameter to [a-zA-Z0-9._-] and add escapeshellarg() in DownloadNewFirmwareAction (command injection) - Add escapeshellarg() to mv command in ConvertAudioFileAction (command injection via temp_filename) - Add realpath() + directory confinement in all four syslog actions to prevent path traversal outside CORE_LOGS_DIR, plus escapeshellarg() on decompression and erase shell commands - Add translated error messages (rest_err_file_*, rest_err_syslog_*, rest_err_firmware_*) across all 26 languages - Add comprehensive TDD security test suite (15 tests)
1 parent 9c91ae1 commit 50dab8e

37 files changed

Lines changed: 1316 additions & 23 deletions

src/AdminCabinet/Controllers/PbxExtensionModulesController.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ public function saveAction(): void
149149
'uniqid' => $data['uniqid'],
150150
'href' => $data['href'],
151151
'group' => $data['menu-group'],
152-
'iconClass' => $data['iconClass'],
152+
'iconClass' => preg_replace('/[^a-zA-Z0-9\s\-_]/', '', $data['iconClass'] ?? ''),
153153
'caption' => $data['caption'],
154154
'showAtSidebar' => $data['show-at-sidebar'] === 'on',
155155
];

src/AdminCabinet/Library/Elements.php

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,8 @@ public function getMenu(): void
328328
$groupHtml .= '<div class="item">';
329329
$groupHtml .= '<div class="header">';
330330
if (array_key_exists('iconclass', $groupparams) && !empty($groupparams['iconclass'])) {
331-
$groupHtml .= "<i class='{$groupparams['iconclass']} icon'></i>";
331+
$escapedIconClass = htmlspecialchars($groupparams['iconclass'], ENT_QUOTES, 'UTF-8');
332+
$groupHtml .= "<i class='{$escapedIconClass} icon'></i>";
332333
}
333334
$groupHtml .= $this->translation->_($groupparams['caption']) . '</div>';
334335
$groupHtml .= "<div class='menu' data-group='$group'>";
@@ -340,8 +341,9 @@ public function getMenu(): void
340341
if (isset($option['data-value'])) {
341342
$groupHtml .= " data-value='{$option['data-value']}'";
342343
}
344+
$escapedIconClass = htmlspecialchars($option['iconclass'], ENT_QUOTES, 'UTF-8');
343345
$groupHtml .= ">
344-
<i class='{$option['iconclass']} icon'></i>{$caption}
346+
<i class='{$escapedIconClass} icon'></i>{$caption}
345347
</a>";
346348

347349
$addToHTML = true;
@@ -352,8 +354,9 @@ public function getMenu(): void
352354
} elseif ($this->ifItPossibleToShowThisElement($group, $groupparams['action'] ?? 'index')) {
353355
$link = $this->getLinkToControllerAction($group, $groupparams['action'], $groupparams['param']);
354356
$caption = $this->translation->_($groupparams['caption']);
357+
$escapedIconClass = htmlspecialchars($groupparams['iconclass'], ENT_QUOTES, 'UTF-8');
355358
$groupHtml .= "<a class='item {$groupparams['style']}' href='$link'>
356-
<i class='{$groupparams['iconclass']} icon'></i>{$caption}
359+
<i class='{$escapedIconClass} icon'></i>{$caption}
357360
</a>";
358361
$addToHTML = true;
359362
}
@@ -381,7 +384,8 @@ public function getIconByController($controllerClass): string
381384
&& array_key_exists('iconclass', $group)
382385
&& !empty($group['iconclass'])
383386
) {
384-
$result = "<i class='{$group['iconclass']} icon'></i>";
387+
$escapedIconClass = htmlspecialchars($group['iconclass'], ENT_QUOTES, 'UTF-8');
388+
$result = "<i class='{$escapedIconClass} icon'></i>";
385389
break;
386390
}
387391
// Group with submenu - search inside submenu
@@ -391,7 +395,8 @@ public function getIconByController($controllerClass): string
391395
$index2 === $controllerClass
392396
&& !empty($submenu['iconclass'])
393397
) {
394-
$result = "<i class='{$submenu['iconclass']} icon'></i>";
398+
$escapedIconClass = htmlspecialchars($submenu['iconclass'], ENT_QUOTES, 'UTF-8');
399+
$result = "<i class='{$escapedIconClass} icon'></i>";
395400
break;
396401
}
397402
}

src/AdminCabinet/Plugins/SecurityPlugin.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,16 @@ public static function isAuthenticated(
169169
// Check for JWT Bearer token in Authorization header (AJAX requests)
170170
$authHeader = $request->getHeader('Authorization');
171171
if ($authHeader && str_starts_with($authHeader, 'Bearer ')) {
172-
// TODO: можно добавить валидацию JWT токена здесь
173-
// Но AuthenticationMiddleware уже проверяет его для API запросов
174-
return true;
172+
$token = substr($authHeader, 7);
173+
$di = \Phalcon\Di\Di::getDefault();
174+
if ($di !== null) {
175+
$jwt = $di->getShared(JwtProvider::SERVICE_NAME);
176+
$payload = $jwt->validate($token);
177+
if ($payload !== null) {
178+
return true;
179+
}
180+
}
181+
// Invalid or expired token — fall through to cookie check
175182
}
176183

177184
// For browser page requests: check for refreshToken cookie

src/Common/Messages/az/RestApi.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,4 +2306,18 @@
23062306
'rest_schema_provider_secret' => 'Provayderlə identifikasiya üçün parol',
23072307
'API Keys' => 'API açarı',
23082308
'Asterisk Managers' => 'AMI istifadəçisi',
2309+
2310+
// Syslog: packet capture and log retrieval
2311+
'rest_syslog_GetCaptureStatus' => 'Paket tutma vəziyyətini al',
2312+
'rest_syslog_GetCaptureStatusDesc' => 'Şəbəkə paket tutmasının (tcpdump) hal-hazırda işlədiyini yoxlayın',
2313+
2314+
// Security: file operation and path validation errors
2315+
'rest_err_file_write_not_permitted' => 'Bu yola yazma icazəsi yoxdur',
2316+
'rest_err_file_invalid_target_dir' => 'Yanlış hədəf qovluğu',
2317+
'rest_err_file_filename_required' => 'Fayl adı tələb olunur',
2318+
'rest_err_file_content_required' => 'Fayl məzmunu tələb olunur',
2319+
'rest_err_file_mkdir_failed' => 'Qovluq yaratmaq alınmadı',
2320+
'rest_err_file_write_failed' => 'Fayl yazmaq alınmadı',
2321+
'rest_err_syslog_invalid_path' => 'Jurnal faylının yolu yanlışdır',
2322+
'rest_err_firmware_invalid_version' => 'Proqram təminatı versiyasının formatı yanlışdır',
23092323
];

src/Common/Messages/cs/RestApi.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,4 +2306,18 @@
23062306
'rest_cf_GetList' => 'Získejte seznam uživatelských souborů',
23072307
'rest_cf_GetRecord' => 'Získání uživatelského souboru podle ID',
23082308
'rest_cf_GetRecordDesc' => 'Získejte podrobné informace o uživatelském souboru, včetně jeho cesty, obsahu a režimu použití.',
2309+
2310+
// Syslog: packet capture and log retrieval
2311+
'rest_syslog_GetCaptureStatus' => 'Získat stav zachytávání paketů',
2312+
'rest_syslog_GetCaptureStatusDesc' => 'Zkontrolujte, zda zachytávání síťových paketů (tcpdump) právě probíhá',
2313+
2314+
// Security: file operation and path validation errors
2315+
'rest_err_file_write_not_permitted' => 'Zápis do této cesty není povolen',
2316+
'rest_err_file_invalid_target_dir' => 'Neplatný cílový adresář',
2317+
'rest_err_file_filename_required' => 'Název souboru je povinný',
2318+
'rest_err_file_content_required' => 'Obsah souboru je povinný',
2319+
'rest_err_file_mkdir_failed' => 'Vytvoření adresáře se nezdařilo',
2320+
'rest_err_file_write_failed' => 'Zápis souboru se nezdařil',
2321+
'rest_err_syslog_invalid_path' => 'Neplatná cesta k souboru protokolu',
2322+
'rest_err_firmware_invalid_version' => 'Neplatný formát verze firmwaru',
23092323
];

src/Common/Messages/da/RestApi.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,4 +2306,18 @@
23062306
'rest_cq_GetRecord' => 'Hent kø efter ID',
23072307
'rest_schema_s3_access_key' => 'S3-adgangsnøgle-ID (synlig i svar)',
23082308
'rest_S3Storage_ApiDescription' => 'Administrer S3-kompatibel cloud-lagring til arkivering af opkaldsoptagelser. Denne singleton-ressource leverer konfiguration til AWS S3, MinIO, Wasabi og andre S3-kompatible lagringstjenester. Funktionerne omfatter automatisk upload af optagelser til cloud-lagring efter udløbet af den lokale opbevaringsperiode, transparent afspilning fra S3-cache og en todelt lagringsstrategi (hot local storage + cold cloud archive). Legitimationsoplysninger krypteres automatisk før lagring.',
2309+
2310+
// Syslog: packet capture and log retrieval
2311+
'rest_syslog_GetCaptureStatus' => 'Hent pakkefangststatus',
2312+
'rest_syslog_GetCaptureStatusDesc' => 'Kontroller om netværkspakkefangst (tcpdump) kører i øjeblikket',
2313+
2314+
// Security: file operation and path validation errors
2315+
'rest_err_file_write_not_permitted' => 'Skrivning til denne sti er ikke tilladt',
2316+
'rest_err_file_invalid_target_dir' => 'Ugyldig målmappe',
2317+
'rest_err_file_filename_required' => 'Filnavn er påkrævet',
2318+
'rest_err_file_content_required' => 'Filindhold er påkrævet',
2319+
'rest_err_file_mkdir_failed' => 'Det lykkedes ikke at oprette mappen',
2320+
'rest_err_file_write_failed' => 'Det lykkedes ikke at skrive filen',
2321+
'rest_err_syslog_invalid_path' => 'Ugyldig sti til logfil',
2322+
'rest_err_firmware_invalid_version' => 'Ugyldigt format for firmwareversion',
23092323
];

src/Common/Messages/de/RestApi.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,4 +2306,18 @@
23062306
'rest_pvd_UpdateStatus' => 'Anbieterstatus aktualisieren',
23072307
'rest_param_emp_export_format' => 'Exportformat: Minimal (7 Felder), Standard (13 Felder), Vollständig (15 Felder)',
23082308
'rest_ms_RefreshToken' => 'OAuth2-Token aktualisieren',
2309+
2310+
// Syslog: packet capture and log retrieval
2311+
'rest_syslog_GetCaptureStatus' => 'Paketerfassungsstatus abrufen',
2312+
'rest_syslog_GetCaptureStatusDesc' => 'Prüfen, ob die Netzwerkpaketerfassung (tcpdump) derzeit läuft',
2313+
2314+
// Security: file operation and path validation errors
2315+
'rest_err_file_write_not_permitted' => 'Schreiben in diesen Pfad ist nicht erlaubt',
2316+
'rest_err_file_invalid_target_dir' => 'Ungültiges Zielverzeichnis',
2317+
'rest_err_file_filename_required' => 'Dateiname ist erforderlich',
2318+
'rest_err_file_content_required' => 'Dateiinhalt ist erforderlich',
2319+
'rest_err_file_mkdir_failed' => 'Verzeichnis konnte nicht erstellt werden',
2320+
'rest_err_file_write_failed' => 'Datei konnte nicht geschrieben werden',
2321+
'rest_err_syslog_invalid_path' => 'Ungültiger Pfad zur Protokolldatei',
2322+
'rest_err_firmware_invalid_version' => 'Ungültiges Format der Firmware-Version',
23092323
];

src/Common/Messages/el/RestApi.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,4 +2306,18 @@
23062306
'rest_cf_GetDefault' => 'Λήψη προεπιλεγμένων τιμών',
23072307
'rest_resource_license' => 'Αδεια',
23082308
'rest_schema_file_filename' => 'Όνομα αρχείου',
2309+
2310+
// Syslog: packet capture and log retrieval
2311+
'rest_syslog_GetCaptureStatus' => 'Λήψη κατάστασης σύλληψης πακέτων',
2312+
'rest_syslog_GetCaptureStatusDesc' => 'Ελέγξτε αν η σύλληψη πακέτων δικτύου (tcpdump) εκτελείται αυτή τη στιγμή',
2313+
2314+
// Security: file operation and path validation errors
2315+
'rest_err_file_write_not_permitted' => 'Η εγγραφή σε αυτή τη διαδρομή δεν επιτρέπεται',
2316+
'rest_err_file_invalid_target_dir' => 'Μη έγκυρος κατάλογος προορισμού',
2317+
'rest_err_file_filename_required' => 'Το όνομα αρχείου είναι υποχρεωτικό',
2318+
'rest_err_file_content_required' => 'Το περιεχόμενο αρχείου είναι υποχρεωτικό',
2319+
'rest_err_file_mkdir_failed' => 'Αποτυχία δημιουργίας καταλόγου',
2320+
'rest_err_file_write_failed' => 'Αποτυχία εγγραφής αρχείου',
2321+
'rest_err_syslog_invalid_path' => 'Μη έγκυρη διαδρομή αρχείου καταγραφής',
2322+
'rest_err_firmware_invalid_version' => 'Μη έγκυρη μορφή έκδοσης firmware',
23092323
];

src/Common/Messages/en/RestApi.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2042,4 +2042,18 @@
20422042
'rest_err_system_query_exception' => 'Failed to execute SQL query',
20432043
'rest_tag_S3Storage' => 'S3 Cloud Storage',
20442044
'rest_schema_system_query' => 'Executed SQL query',
2045+
2046+
// Security: file operation error messages
2047+
'rest_err_file_write_not_permitted' => 'Write not permitted to this path',
2048+
'rest_err_file_invalid_target_dir' => 'Invalid target directory',
2049+
'rest_err_file_filename_required' => 'Filename is required',
2050+
'rest_err_file_content_required' => 'File content is required',
2051+
'rest_err_file_mkdir_failed' => 'Failed to create directory',
2052+
'rest_err_file_write_failed' => 'Failed to write file',
2053+
2054+
// Security: syslog path validation
2055+
'rest_err_syslog_invalid_path' => 'Invalid log file path',
2056+
2057+
// Security: firmware version validation
2058+
'rest_err_firmware_invalid_version' => 'Invalid firmware version format',
20452059
];

src/Common/Messages/es/RestApi.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,4 +2306,18 @@
23062306
'rest_cf_GetRecord' => 'Obtener archivo de usuario por ID',
23072307
'rest_resource_file' => 'Archivo',
23082308
'rest_param_pk_user_id' => 'ID de usuario',
2309+
2310+
// Syslog: packet capture and log retrieval
2311+
'rest_syslog_GetCaptureStatus' => 'Obtener estado de captura de paquetes',
2312+
'rest_syslog_GetCaptureStatusDesc' => 'Verificar si la captura de paquetes de red (tcpdump) está en ejecución actualmente',
2313+
2314+
// Security: file operation and path validation errors
2315+
'rest_err_file_write_not_permitted' => 'No se permite escribir en esta ruta',
2316+
'rest_err_file_invalid_target_dir' => 'Directorio de destino no válido',
2317+
'rest_err_file_filename_required' => 'El nombre de archivo es obligatorio',
2318+
'rest_err_file_content_required' => 'El contenido del archivo es obligatorio',
2319+
'rest_err_file_mkdir_failed' => 'No se pudo crear el directorio',
2320+
'rest_err_file_write_failed' => 'No se pudo escribir el archivo',
2321+
'rest_err_syslog_invalid_path' => 'Ruta de archivo de registro no válida',
2322+
'rest_err_firmware_invalid_version' => 'Formato de versión de firmware no válido',
23092323
];

0 commit comments

Comments
 (0)