GHSA-grm4-wm43-9jh5 — contao/contao
LOWGHSA-grm4-wm43-9jh5 is a low-severity (CVSS 3.1) Path Traversal vulnerability in contao/contao. A fix is available for contao/contao — see the affected versions and patch details below.
Contao: Possible path traversal in job download URIs
Exploitation Status
No confirmed exploitation observed yet
- CISA’s own triage has not observed active exploitation or public proof-of-concept code for this CVE as of its last assessment.
Exploitation and automatability from CISA’s SSVC triage for GHSA-grm4-wm43-9jh5.
EPSS Exploitation Probability
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-grm4-wm43-9jh5 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 377,333 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
contao/contao🐘contao/core-bundleReal-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
An authenticated backend user who can access one job can request an attachment identifier containing ../ segments and make the job attachment download endpoint read a file from another job directory inside var/job-attachments.
The controller authorizes only the jobUuid route parameter. The later attachment lookup joins that authorized job UUID with the attacker-controlled identifier, then passes the combined path to the virtual filesystem. VirtualFilesystem::resolve() canonicalizes the whole path and only rejects paths that escape the filesystem mount, so authorized-job/../victim-job/debug_log.csv becomes victim-job/debug_log.csv.
This is a cross-job authorization bypass for known job attachment paths. It is not a practical brute-force against unknown jobs because job directories are UUID v4 values.
Root Cause
JobsController::downloadJobAttachment() checks access to the route jobUuid before loading the attachment:
$job = $this->jobs->getByUuid($jobUuid);
if (!$job || !$this->jobs->hasAccess($job)) {
throw $this->createNotFoundException();
}
$attachment = $this->jobs->getAttachment($jobUuid, $identifier);
Jobs::getAttachment() then resolves a path built from the authorized job UUID and the attacker-controlled identifier:
$fileItem = $this->jobAttachmentsStorage->get($this->getAttachmentIdentifier($job, $identifier));
return $job->getUuid().'/'.$identifier;
VirtualFilesystem::resolve() canonicalizes the combined path. It rejects absolute paths and paths that start with .., but it does not preserve the authorized job directory as a boundary:
$path = Path::canonicalize($location);
if (str_starts_with($path, '..')) {
throw new \OutOfBoundsException(...);
}
return Path::join($this->prefix, $path);
Therefore:
<authorized-job>/../<victim-job>/debug_log.csv
canonicalizes to:
<victim-job>/debug_log.csv
which remains inside the job-attachments filesystem mount and is accepted.
Recommended Fix
Treat the attachment identifier as a filename, not a path:
- Reject
/,\, NUL, and dot-segment components inidentifier. - Add a route requirement that prevents slashes in
{identifier}if nested attachment paths are not intended. - After resolving, assert the canonical relative path starts with
<authorized-job-uuid>/before returning aFilesystemItem. - Apply the same identifier validation in
Jobs::addAttachment()so future producers/extensions cannot write outside the owning job directory.
Impact
A low-privileged backend user can read another job's attachment if they know or obtain the target job UUID and attachment filename. Built-in crawler jobs attach CSV logs such as debug_log.csv, broken-link-checker_log.csv, and search-index_log.csv, which can contain crawled URLs, referring URLs, tags, and error messages.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐘Packagist | contao/contao | ≥ 5.7.0&&< 5.7.7 | 5.7.7composer require contao/contao:^5.7.7 |
| 🐘Packagist | contao/core-bundle | ≥ 5.7.0&&< 5.7.7 | 5.7.7composer require contao/core-bundle:^5.7.7 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for contao/contao, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update contao/contao to 5.7.7 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-grm4-wm43-9jh5 is resolved across your whole dependency graph.
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.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-grm4-wm43-9jh5 can be triaged on real exposure rather than presence alone.
Tailored to GHSA-grm4-wm43-9jh5. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-grm4-wm43-9jh5 in your dependencies?
O3 Security finds GHSA-grm4-wm43-9jh5 across Packagist dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.