PeakCyber Technologies
PeakCyber Technologies

Zip Slip Vulnerability Explained: Path Traversal

Zip Slip attack: archive entry with ../ path escaping the extraction directory

Zip Slip: The Silent Threat Inside an Ordinary Archive File

Picture a web application where users can upload a .zip file and have its contents extracted on the server. CV upload systems, theme/plugin installers, backup tools, CI/CD pipelines… they all use this pattern: “take the archive, open it, write its contents to disk.”

This is exactly where a vulnerability that’s been known for years — but was brought to wide attention by Snyk’s comprehensive 2018 research — comes into play: Zip Slip.

What Is Zip Slip?

Zip Slip is a directory traversal and arbitrary file write vulnerability that arises when opening an archive file (zip, tar, jar, war, rar, 7z, apk, cpio…). If a file name inside the archive contains sequences like ../../ and the application writes that name to disk without validating it, an attacker can make the file get written outside the intended folder, anywhere on the system.

Snyk’s 2018 research found this pattern in thousands of projects across many ecosystems, including Java, JavaScript, .NET, Go, and Ruby. The reason is interesting: many languages lack an official, standardized high-level library for extracting archives, so developers have spent years copying the same vulnerable snippet of code from one another (StackOverflow has a big role here). The archive/zip package is no exception — it performs no validation at all and leaves the control entirely up to you.

We initially decided to use Split Query for all our heavy queries. But after a while, we noticed response times were growing significantly on our paginated endpoints — lists returning between 10 and 100 records. Why? Because joining 15 different tables for 10 records (Single Query) is blazing fast, whereas firing 16 separate queries for those same 10 records (Split Query) was causing slowness due to network round-trip latency.

Vulnerable Code

The function below shows the essence of a typical unzip operation:

func unzip(src, destDir string) error {
r, _ := zip.OpenReader(src)
defer r.Close()

for _, f := range r.File {
// VULNERABLE: target path is built without validating f.Name
targetPath := filepath.Join(destDir, f.Name)

outFile, _ := os.OpenFile(targetPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
rc, _ := f.Open()
io.Copy(outFile, rc)
outFile.Close()
rc.Close()
}
return nil
}

The root of the problem is the line targetPath := filepath.Join(destDir, f.Name). Here, the value of f.Name is read directly from inside the ZIP archive and is entirely under the attacker’s control. If an attacker crafts a file name containing a directory traversal sequence like ../../../etc/cron.d/evil, filepath.Join() will normalize that path — but it does not verify whether the resulting final path still stays under destDir. As a result, the application can end up writing the file to any location outside the target directory, which leads to a classic Zip Slip vulnerability.

The impact varies depending on the target: overwriting a cron file to run a scheduled job, modifying a startup script, adding your own key to the authorized_keys file, or tampering with the application’s own executable files to achieve remote code execution (RCE). What looks like “just a file-copy bug” can often escalate all the way to full server compromise.

Attack Scenario: Step by Step

Suppose an application extracts user-uploaded ZIP files into the /var/www/app/uploads/themes/ directory and uses a vulnerable unzip()function.

The attacker adds a malicious entry containing a directory traversal sequence, such as ../../../../../tmp/zipslip_poc.txt, alongside a normal style.css file in the archive. Since standard archiving tools typically block names like this, the attacker programmatically generates the ZIP file using a library such as archive/zip. Once the archive is uploaded to the application, if sufficient path validation isn’t performed, the malicious file gets written outside the theme folder.

When unzip() runs on the server, the style.css file is written to the uploads/themes/ directory as expected. However, when the ../../../../../tmp/zipslip_poc.txt entry is processed, filepath.Join()normalizes the path and moves the target to the location /tmp/zipslip_poc.txt. Since the code doesn’t check whether the resulting path goes outside the target directory, it creates the file at this location.

So while the application thinks it’s just uploading a theme, the attacker can write a file to any location the process’s permissions allow. In a real attack, the target might be cron directories, service scripts, or SSH key files instead of /tmp. In that case, the arbitrary file write vulnerability can, under the right conditions, escalate into remote code execution.

A Real-World Example: CVE-2025-62156

This scenario might seem hypothetical, but the exact same pattern was found very recently in a real product. In October 2025, a Zip Slip vulnerability was discovered in Argo Workflows — a popular open-source project under the CNCF umbrella used for workflow orchestration on Kubernetes: CVE-2025-62156 (GHSA-p84v-gxvw-73pf, CVSS 8.1 -High).

The source of the vulnerability is the untar() function in the workflow/executor/executor.go file; the target path is generated with exactly this line:

target := filepath.Join(dest, filepath.Clean(header.Name))

The exact same mistake as in our example above: filepath.Clean simplifies the path but doesn’t prevent it from escaping dest. Argo Workflows can download “artifacts” as tar.gz files for workflow steps and extract them into a specific folder (/work/tmp); researchers demonstrated that an entry name starting with ../ could reach the container’s /etc/ directory via a shared volume mount. The result: critical files like /etc/passwd, /etc/hosts, and /etc/crontab could be overwritten-meaning a bypass of container isolation and potential privilege escalation. The vulnerability affected all versions prior to 3.6.12, as well as the 3.7.0–3.7.2 range; it was fixed in 3.6.12 and 3.7.3.

The Right Way: Validating the Target Path

The fix comes down to a single check: before writing each file, verify that the resulting target path is actually inside the target folder.

destDirAbs, _ := filepath.Abs(destDir)

for _, f := range r.File {
targetPath := filepath.Join(destDirAbs, f.Name)

// SECURITY CHECK: is the target path within destDir’s boundaries?
if !strings.HasPrefix(targetPath, destDirAbs+string(os.PathSeparator)) {
return fmt.Errorf(“zip slip attempt detected: %q”, f.Name)
}
// … the code that writes the file to targetPath stays unchanged
}

The critical point is that filepath.Join() alone is not a security mechanism. This function only normalizes the path; it does not verify that the resulting final path still stays within destDir‘s boundaries. The actual protection comes from the strings.HasPrefix(targetPath, destDirAbs+string(os.PathSeparator)) check, which verifies that the target path starts with destDir. Adding os.PathSeparator here matters too; otherwise, a directory name like /home/app/uploads-evil could be mistakenly accepted as safe just because it starts with /home/app/uploads.

Not Just .zip: A Wide Family

The same logical flaw can also show up in code that opens the following formats:

  • tar / tar.gz → the same check is needed when working with archive/tar; some sources call this “Tar Slip”
  • jar / war → especially common in the Java world
  • apk → Android packages
  • rar, 7z, cpio → the same principle applies

Saying “we don’t use zip, this doesn’t concern us” can be misleading. Every piece of code that opens any archive format needs to be checked for this.

How to Detect It?

  • Static code analysis: Semgrep, CodeQL, and Snyk Code can catch this pattern.
  • Dependency scanning: Check the archive libraries you use for known CVE records.
  • Manual code review: Look for expressions like zip.OpenReader, tar.NewReader, filepath.Join; check whether a path validation immediately follows them.
  • Fuzzing / pentesting: Preparing a test archive with an entry name containing ../../ and observing the behavior is the fastest way to validate.

Defense Checklist

  1. Convert the target path to an absolute path for every entry, and verify it stays strictly within destDir (HasPrefix(target, destDirAbs+separator), not just Contains).
  2. Also consider symbolic links; even if an entry doesn’t contain .., the target can be escaped via a previously created symlink.
  3. Consider isolating archives from untrusted sources and extracting them in a low-privilege environment (sandbox/container).
  4. Write unit tests using test archives containing entry names like ../../evil.

Conclusion

Zip Slip is a classic example of “a small detail was missed, and a big vulnerability was born.” Trusting the contents of an archive means trusting data under the attacker’s control. filepath.Join gives you “cleanup,” but not a “boundary” — confusing the two is the most common root cause of Zip Slip. The rule is always the same:

Never use unvalidated user input in file system operations.

 

PeakCyber cybersecurity and offensive security services logo.

Subscribe to our cyber security newsletter

Scroll to top