Skip to content

Path Traversal in Toggl ZIP Data Importer - arbitrary directory creation + filesystem existence oracle

Moderate
Onatcer published GHSA-cj49-589g-wp6j Jul 9, 2026

Package

composer solidtime (Composer)

Affected versions

<= 0.15.1

Patched versions

0.16.0

Description

Summary

TogglDataImporter::importData() extracts an uploaded ZIP and reads projects.json with no schema/type validation. Each project object's id field - fully attacker-controlled - is concatenated directly into filesystem paths (projects_users/<id>.json, tasks/<id>.json) with no sanitization:

// app/Service/Import/Importers/TogglDataImporter.php:149-153
if (! file_exists($temporaryDirectory->path('projects_users/'.$project->id.'.json'))) {
    throw new ImportException('File "projects_users/'.$project->id.'.json" missing in ZIP');
}

Spatie\TemporaryDirectory\TemporaryDirectory::path() (a third-party dependency) doesn't just concatenate strings - it actively mkdir()s any missing parent directory in the resolved path (vendor/spatie/temporary-directory/src/TemporaryDirectory.php:83-98, permission 0777 before umask). Because $project->id can contain ../ sequences, the resolved path can escape the intended temp sandbox entirely, and the missing-directory auto-creation turns what looks like a read-only file_exists() check into a real filesystem write primitive.

ImportController also returns the raw ImportException message verbatim to the API caller, and a different generic error ("Unknown error") surfaces when the resolved target already exists (PHP's mkdir() raising E_WARNING "File exists"). Together these two distinguishable responses give a reliable existence oracle for arbitrary paths on the host.

Proof of Concept

Both of the following were reproduced live, end-to-end, through the real HTTP API - see the attached script for the exact automated reproduction.

Arbitrary directory creation.** A ZIP with a projects.json entry "id": "../../../../../../var/www/html/PATH_TRAVERSAL_POC_<marker>/probe" was submitted via POST /api/v1/organizations/{org}/import (type=toggl_data_importer). The target directory was confirmed absent beforehand and present immediately afterward - independently verified on
the container filesystem (stat), not just inferred from the response.

Filesystem existence oracle.** Submitting an id that resolves to an already-existing path (../../../../../../var/www/html/composer, the importer always appends .json) produces a distinguishable {"message":"Unknown error"} response instead of the "...missing in ZIP" message used for non-existent targets - reliably answering "does this path exist?" for any attacker-chosen path.

Impact

  • Arbitrary, 0755 world-readable directory creation anywhere the application's runtime user can write - usable for minor DoS (inode/directory-entry exhaustion via repeated abuse) or to plant directories at paths other code may assume are plain files.
  • A reconnaissance oracle for the existence of arbitrary paths on the host (installation layout, sibling containers' bind mounts, etc.).
  • Reachable by any registered user via self-service organization creation
  • No elevated privileges or victim interaction required.

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
Low
Availability
Low

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L

CVE ID

No known CVE

Weaknesses

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. Learn more on MITRE.

Credits