{"id":"CVE-2026-55825","aliases":["EUVD-2026-51620","GHSA-grm4-wm43-9jh5"],"status":"published","title":"Contao: Possible path traversal in job download URIs","description":"Contao is an Open Source CMS. In versions 5.7.0 through 5.7.6, 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.","severity":{"version":"3.1","score":3.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N","level":"low","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00296,"percentile":0.22408,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-22"],"published":"2026-07-31T19:07:40Z","modified":"2026-10-28T02:30:35Z","fixes":[{"ecosystem":"Packagist","package":"contao/contao","fixed_in":["5.7.7"],"sources":["ghsa","osv"]},{"ecosystem":"Packagist","package":"contao/core-bundle","fixed_in":["5.7.7"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Packagist","package":"contao/contao","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.7.0"},{"fixed":"5.7.7"}]}]},{"source":"ghsa","ecosystem":"Packagist","package":"contao/core-bundle","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.7.0"},{"fixed":"5.7.7"}]}]},{"source":"osv","ecosystem":"Packagist","package":"contao/contao","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.7.0"},{"fixed":"5.7.7"}]}],"versions":["5.7.0","5.7.1","5.7.2","5.7.3","5.7.4","5.7.5","5.7.6"]},{"source":"osv","ecosystem":"Packagist","package":"contao/core-bundle","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.7.0"},{"fixed":"5.7.7"}]}],"versions":["5.7.0","5.7.1","5.7.2","5.7.3","5.7.4","5.7.5","5.7.6"]}],"products":[{"vendor":"contao","product":"contao"}],"references":[{"url":"https://github.com/contao/contao/security/advisories/GHSA-grm4-wm43-9jh5","tags":["x_refsource_CONFIRM","advisory","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-55825","tags":["advisory"]},{"url":"https://contao.org/en/security-advisories/path-traversal-in-the-jobs-module","tags":["web"]},{"url":"https://github.com/FriendsOfPHP/security-advisories/blob/master/contao/contao/CVE-2026-55825.yaml","tags":["web"]},{"url":"https://github.com/FriendsOfPHP/security-advisories/blob/master/contao/core-bundle/CVE-2026-55825.yaml","tags":["web"]},{"url":"https://github.com/advisories/GHSA-grm4-wm43-9jh5"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/55xxx/CVE-2026-55825.json","tags":["advisory"]},{"url":"https://github.com/contao/contao","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-55825","title":"Contao: Possible path traversal in job download URIs","description":"Contao is an Open Source CMS. In versions 5.7.0 through 5.7.6, 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.","status":"published","published":"2026-07-31T19:07:40.609Z","modified":"2026-07-31T23:33:16.171Z","cvss":[{"version":"3.1","score":3.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N","level":"low","assessor":"cna"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T14:20:12.260247Z"}],"euvd":[{"id":"EUVD-2026-51620","description":"Contao is an Open Source CMS. In versions 5.7.0 through 5.7.6, 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.","status":"published","published":"2026-07-31T19:07:40Z","modified":"2026-07-31T23:33:16Z","cvss":[{"version":"3.1","score":3.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N","level":"low"}],"updated_at":"2026-11-02T18:02:25.159179Z"}],"ghsa":[{"id":"GHSA-grm4-wm43-9jh5","title":"Contao: Possible path traversal in job download URIs","description":"## Summary\n\nAn 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`.\n\nThe 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`.\n\nThis 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.\n\n\n## Root Cause\n\n`JobsController::downloadJobAttachment()` checks access to the route `jobUuid` before loading the attachment:\n\n```php\n$job = $this-\u003ejobs-\u003egetByUuid($jobUuid);\n\nif (!$job || !$this-\u003ejobs-\u003ehasAccess($job)) {\n    throw $this-\u003ecreateNotFoundException();\n}\n\n$attachment = $this-\u003ejobs-\u003egetAttachment($jobUuid, $identifier);\n```\n\n`Jobs::getAttachment()` then resolves a path built from the authorized job UUID and the attacker-controlled identifier:\n\n```php\n$fileItem = $this-\u003ejobAttachmentsStorage-\u003eget($this-\u003egetAttachmentIdentifier($job, $identifier));\n```\n\n```php\nreturn $job-\u003egetUuid().'/'.$identifier;\n```\n\n`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:\n\n```php\n$path = Path::canonicalize($location);\n\nif (str_starts_with($path, '..')) {\n    throw new \\OutOfBoundsException(...);\n}\n\nreturn Path::join($this-\u003eprefix, $path);\n```\n\nTherefore:\n\n```text\n\u003cauthorized-job\u003e/../\u003cvictim-job\u003e/debug_log.csv\n```\n\ncanonicalizes to:\n\n```text\n\u003cvictim-job\u003e/debug_log.csv\n```\n\nwhich remains inside the `job-attachments` filesystem mount and is accepted.\n\n\n## Recommended Fix\n\nTreat the attachment identifier as a filename, not a path:\n\n- Reject `/`, `\\`, NUL, and dot-segment components in `identifier`.\n- Add a route requirement that prevents slashes in `{identifier}` if nested attachment paths are not intended.\n- After resolving, assert the canonical relative path starts with `\u003cauthorized-job-uuid\u003e/` before returning a `FilesystemItem`.\n- Apply the same identifier validation in `Jobs::addAttachment()` so future producers/extensions cannot write outside the owning job directory.\n\n### Impact\nA 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.","status":"published","published":"2026-08-06T19:49:59Z","modified":"2026-08-06T19:50:01Z","cvss":[{"version":"3.1","score":3.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N","level":"low"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T14:40:57.999375Z"}],"nvd":[{"id":"CVE-2026-55825","description":"Contao is an Open Source CMS. In versions 5.7.0 through 5.7.6, 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.","status":"published","published":"2026-07-31T20:16:52.343Z","modified":"2026-09-08T20:51:43.49Z","cvss":[{"version":"3.1","score":3.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N","level":"low","assessor":"secondary"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T14:12:34.290716Z"}],"osv":[{"id":"CVE-2026-55825","title":"Contao: Possible path traversal in job download URIs","description":"Contao is an Open Source CMS. In versions 5.7.0 through 5.7.6, 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.","status":"published","published":"2026-07-31T19:07:40.609Z","modified":"2026-10-28T02:30:35.460433978Z","cvss":[{"version":"3.1","score":3.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N","level":"low"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T15:45:11.609219Z"},{"id":"GHSA-grm4-wm43-9jh5","title":"Contao: Possible path traversal in job download URIs","description":"## Summary\n\nAn 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`.\n\nThe 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`.\n\nThis 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.\n\n\n## Root Cause\n\n`JobsController::downloadJobAttachment()` checks access to the route `jobUuid` before loading the attachment:\n\n```php\n$job = $this-\u003ejobs-\u003egetByUuid($jobUuid);\n\nif (!$job || !$this-\u003ejobs-\u003ehasAccess($job)) {\n    throw $this-\u003ecreateNotFoundException();\n}\n\n$attachment = $this-\u003ejobs-\u003egetAttachment($jobUuid, $identifier);\n```\n\n`Jobs::getAttachment()` then resolves a path built from the authorized job UUID and the attacker-controlled identifier:\n\n```php\n$fileItem = $this-\u003ejobAttachmentsStorage-\u003eget($this-\u003egetAttachmentIdentifier($job, $identifier));\n```\n\n```php\nreturn $job-\u003egetUuid().'/'.$identifier;\n```\n\n`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:\n\n```php\n$path = Path::canonicalize($location);\n\nif (str_starts_with($path, '..')) {\n    throw new \\OutOfBoundsException(...);\n}\n\nreturn Path::join($this-\u003eprefix, $path);\n```\n\nTherefore:\n\n```text\n\u003cauthorized-job\u003e/../\u003cvictim-job\u003e/debug_log.csv\n```\n\ncanonicalizes to:\n\n```text\n\u003cvictim-job\u003e/debug_log.csv\n```\n\nwhich remains inside the `job-attachments` filesystem mount and is accepted.\n\n\n## Recommended Fix\n\nTreat the attachment identifier as a filename, not a path:\n\n- Reject `/`, `\\`, NUL, and dot-segment components in `identifier`.\n- Add a route requirement that prevents slashes in `{identifier}` if nested attachment paths are not intended.\n- After resolving, assert the canonical relative path starts with `\u003cauthorized-job-uuid\u003e/` before returning a `FilesystemItem`.\n- Apply the same identifier validation in `Jobs::addAttachment()` so future producers/extensions cannot write outside the owning job directory.\n\n### Impact\nA 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.","status":"published","published":"2026-08-06T19:49:59Z","modified":"2026-08-06T20:00:15.424968922Z","cvss":[{"version":"3.1","score":3.1,"vector":"CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:N","level":"low"}],"cwes":["CWE-22"],"updated_at":"2026-11-02T15:46:48.191583Z"}]}}
