HIGH zip slipecho gofirestore

Zip Slip in Echo Go with Firestore

Zip Slip in Echo Go with Firestore — how this specific combination creates or exposes the vulnerability

A Zip Slip vulnerability occurs when an archive extraction uses user-supplied paths without proper sanitization, enabling path traversal (e.g., ../../../etc/passwd) to escape the intended extraction directory. In an Echo Go service that integrates with Firestore, this risk can appear in two places: (1) server-side extraction of uploaded archives before storing documents or files in Firestore, and (2) client-side archive generation built by the API where the API dynamically creates ZIP archives using paths derived from Firestore document fields or IDs.

If an Echo Go handler accepts an archive upload, extracts it with a library such as github.com/mholt/archiver/v3, and uses values from Firestore (e.g., a document ID or a user-provided metadata field) to name files inside the archive, untrusted input can traverse directories during extraction. For example, a document field exportFileName stored in Firestore might be used to name a file inside a ZIP built by the API; if that field contains ../../malicious.sh, the generated archive becomes a weapon for Zip Slip. Conversely, when the API serves downloads by extracting user-provided archives and writing files to disk under a Firestore-managed directory, unsanitized archive entries can overwrite arbitrary files outside the target folder, potentially affecting Firestore-side metadata or related resources if the runtime uses predictable paths derived from document IDs.

In this stack, the typical chain is: a client uploads an archive to an Echo Go endpoint; the handler reads Firestore to determine context (project ID, user ID, or output naming); the handler extracts or builds a ZIP using a combination of Firestore metadata and user input; and improper path validation allows directory traversal. Because the vulnerability is a construction/usage issue rather than a Firestore or Echo bug, remediation must focus on path sanitization and strict allowlisting when combining Firestore-derived values with filesystem paths or archive entries.

Concrete example (unsafe):

// Unsafe: using Firestore document field directly in archive path
func unsafeExportHandler(c echo.Context) error {
    docID := c.Param("docID")
    ctx := context.Background()
    client, err := firestore.NewClient(ctx, "my-project")
    if err != nil { return err }
    defer client.Close()

    doc, err := client.Collection("exports").Doc(docID).Get(ctx)
    if err != nil { return err }
    filename, _ := doc.Data()["fileName"].(string) // user-controlled

    // Build a zip using filename from Firestore without validation
    buf := new(bytes.Buffer)
    z := zip.NewWriter(buf)
    f, _ := z.File.Create(filename) // Zip Slip possible
    io.WriteString(f, "content")
    z.Close()

    return c.Blob(http.StatusOK, "application/zip", buf.Bytes())
}

In this pattern, filename pulled from a Firestore document can contain path traversal sequences, and creating a file entry with z.File.Create will honor those paths inside the archive, enabling Zip Slip. An attacker could craft a Firestore document with fileName: "../../secrets.txt" and cause extraction routines elsewhere to escape intended directories.

Firestore-Specific Remediation in Echo Go — concrete code fixes

Remediation focuses on sanitizing any path derived from Firestore before using it in filesystem operations or archive creation. Always validate and normalize paths, use a secure base directory, and prefer generating safe names rather than trusting stored values.

1) Validate and clean filenames before archive creation:

import (
    "archive/zip"
    "io"
    "net/http"
    "path/filepath"
    "strings"

    "github.com/labstack/echo/v4"
    "google.golang.org/api/option"
    firestore "cloud.google.com/go/firestore"
)

func safeExportHandler(c echo.Context) error {
    docID := c.Param("docID")
    ctx := context.Background()
    client, err := firestore.NewClient(ctx, "my-project", option.WithoutAuthentication())
    if err != nil { return err }
    defer client.Close()

    doc, err := client.Collection("exports").Doc(docID).Get(ctx)
    if err != nil { return err }
    raw, ok := doc.Data()["fileName"].(string)
    if !ok || raw == "" {
        return c.String(http.StatusBadRequest, "missing fileName")
    }

    // Normalize and restrict to a safe basename
    clean := filepath.Base(strings.TrimSpace(raw))
    if clean == "." || clean == ".." || clean == "" {
        return c.String(http.StatusBadRequest, "invalid fileName")
    }

    buf := new(bytes.Buffer)
    z := zip.NewWriter(buf)
    f, err := z.File.Create(clean) // safe: clean basename
    if err != nil { return err }
    _, err = io.WriteString(f, "content")
    if err != nil { return err }
    err = z.Close()
    if err != nil { return err }

    return c.Blob(http.StatusOK, "application/zip", buf.Bytes())
}

2) When extracting archives destined for Firestore-related directories, enforce a strict output directory and reject paths that escape it:

import (
    "archive/zip"
    "io"
    "os"
    "path/filepath"
)

const baseDir = "/safe/extract"

func extractAndStore(zipData []byte) error {
    r, err := zip.OpenReader("", bytes.NewReader(zipData))
    if err != nil { return err }
    defer r.Close()

    for _, f := range r.File {
        // Clean and ensure within baseDir
        destPath := filepath.Join(baseDir, filepath.Clean(f.Name))
        if !strings.HasPrefix(destPath, filepath.Clean(baseDir)+string(os.PathSeparator)) && destPath != filepath.Clean(baseDir) {
            return fmt.Errorf("zip entry path traversal blocked: %s", f.Name)
        }
        // Proceed to extract or to store content in Firestore using a sanitized name
        // For example, store f.Name in Firestore only after validation
    }
    return nil
}

3) Use allowlists for Firestore-derived identifiers used in filenames or keys. If filenames are not required to be user-readable, replace them with UUIDs or Firestore document IDs and store the original name as metadata only.

func storeWithSafeKey(c echo.Context) error {
    docID := c.Param("docID")
    ctx := context.Background()
    client, err := firestore.NewClient(ctx, "my-project")
    if err != nil { return err }
    defer client.Close()

    _, _, err = client.Collection("exports").Doc(docID).Create(ctx, map[string]interface{}{
        "safeKey":  uuid.NewString(), // store generated key
        "userName": "alice",
    })
    return err
}

Frequently Asked Questions

Can Firestore itself prevent Zip Slip attacks?
Firestore does not process file paths or archives, so it cannot prevent Zip Slip. The vulnerability is in how your Echo Go code uses Firestore data to construct paths or filenames; mitigation must be implemented in application logic.
Does middleBrick detect Zip Slip in Echo Go APIs that use Firestore?
middleBrick scans unauthenticated attack surfaces and tests endpoints; it can identify risky endpoint behaviors and surface findings related to path traversal and injection. Findings include remediation guidance to validate and sanitize inputs derived from Firestore before using them in archives or filesystem paths.