Zip Slip in Fiber with Hmac Signatures
Zip Slip in Fiber with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Zip Slip is a path traversal vulnerability that occurs when an application constructs destination paths by directly concatenating user-supplied archive entries with a base extraction directory. In the Fiber web framework for Go, this typically appears in HTTP handlers that accept uploaded archives or process archive metadata. Introducing Hmac Signatures into this context—such as using HMACs to verify integrity or authenticity of requests, headers, or tokens—does not prevent path traversal; it can inadvertently obscure the risk by creating a false sense of integrity. For example, a handler might validate an HMAC on a request parameter containing a filename or archive path, then proceed to extract the archive without normalizing or sanitizing the entry names. Because the HMAC only confirms the parameter was not tampered with (by a trusted party), the developer may assume the contents are safe and skip path sanitization. This combination creates a vulnerability window: an attacker can supply a specially crafted archive with malicious paths (e.g., ../../../etc/passwd) and a valid HMAC if the secret is weak or the HMAC is applied only to a subset of inputs. The scan checks for unsafe archive consumption and path traversal patterns; findings may highlight missing entry validation even when Hmac Signatures are present, because the signature does not constrain file paths within the archive.
Consider a scenario where an API endpoint accepts a signed token indicating the allowed extraction prefix. If the token is validated via Hmac Signatures but the archive processing logic uses the token value to build output paths without canonicalization, an attacker can supply entries like ../../malicious. The signature remains valid, but the resolved path escapes the intended directory. This is a BFLA/Privilege Escalation and Property Authorization issue in the context of the 12 checks, because the effective permissions do not match the intended authorization boundary. The scanner tests unauthenticated attack surfaces, so it can identify whether archive extraction routines properly resolve paths independent of signature status. Data Exposure and Unsafe Consumption checks may also flag scenarios where sensitive file contents are extracted due to insufficient path checks, even when request-level Hmac Signatures are verified.
In summary, using Hmac Signatures in Fiber does not mitigate Zip Slip; it can create a false positive security signal while the underlying path traversal persists. The scanner evaluates the runtime behavior of archive handling and path resolution, ensuring findings are based on actual traversal risks rather than cryptographic assurances that do not constrain file system operations.
Hmac Signatures-Specific Remediation in Fiber — concrete code fixes
To remediate Zip Slip in Fiber while properly using Hmac Signatures, you must validate and sanitize archive entry paths independently of signature checks. Hmac Signatures should be used to authenticate the request or token, but path normalization, prefix enforcement, and rejection of malicious sequences must be implemented explicitly. Below are concrete, syntactically correct examples for Fiber in Go that demonstrate secure handling.
// Example 1: Validate HMAC on a header and sanitize archive entries before extraction
package main
import (
"archive/zip"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"path/filepath"
"strings"
"github.com/gofiber/fiber/v2"
)
const secret = "your-secure-secret"
func verifyHMAC(payload, receivedMAC string) bool {
key := []byte(secret)
mac := hmac.New(sha256.New, key)
mac.Write([]byte(payload))
expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(receivedMAC))
}
func sanitizeEntryName(name, baseDir string) (string, error) {
// Clean and ensure the path stays within baseDir
clean := filepath.Clean(name)
if strings.Contains(clean, "..") || strings.HasPrefix(clean, "/") {
return "", ErrInvalidPath
}
full := filepath.Join(baseDir, clean)
if !strings.HasPrefix(full, filepath.Clean(baseDir)+string(filepath.Separator)) && full != filepath.Clean(baseDir) {
return "", ErrInvalidPath
}
return full, nil
}
var ErrInvalidPath = &fiber.StatusError{
Code: http.StatusBadRequest,
Message: "invalid path in archive entry",
}
func uploadHandler(c *fiber.Ctx) error {
// Assume the HMAC is provided in a custom header
signature := c.Get("X-Request-Hmac")
// Create a canonical payload string from selected request elements, e.g., timestamp + headers
// In practice, use a consistent method agreed with the client
payload := "example-payload"
if !verifyHMAC(payload, signature) {
return c.Status(http.StatusUnauthorized).SendString("invalid signature")
}
file, err := c.FormFile("archive")
if err != nil {
return c.Status(http.StatusBadRequest).SendString("archive required")
}
src, err := file.Open()
if err != nil {
return c.Status(http.StatusInternalServerError).SendString("failed to open archive")
}
defer src.Close()
zipReader, err := zip.NewReader(src, file.Size)
if err != nil {
return c.Status(http.StatusBadRequest).SendString("invalid archive")
}
baseDir := "/safe/extraction/path"
for _, f := range zipReader.File {
target, err := sanitizeEntryName(f.Name, baseDir)
if err != nil {
return ErrInvalidPath
}
// Proceed to extract to target
// Use io.Copy and proper file creation with restricted permissions
}
return c.SendStatus(http.StatusOK)
}
The key points are: verify Hmac Signatures on a canonical representation of the request to ensure authenticity, then treat archive entry names as untrusted input. Use filepath.Clean, reject entries containing .., and enforce a base directory prefix check to prevent directory traversal. This approach keeps cryptographic integrity checks separate from path validation, eliminating the false security that Hmac Signatures alone might provide.
// Example 2: Enforce strict prefix and reject dangerous patterns in Fiber routes
package main
import (
"archive/zip"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/gofiber/fiber/v2"
)
func verifyHMAC(data, mac string) bool {
key := []byte(os.Getenv("HMAC_SECRET"))
hash := hmac.New(sha256.New, key)
hash.Write([]byte(data))
return hmac.Equal([]byte(hex.EncodeToString(hash.Sum(nil))), []byte(mac))
}
func extractHandler(c *fiber.Ctx) error {
mac := c.Get("X-Hub-Signature-256")
if !verifyHMAC(c.Request().Header.Peek("X-Timestamp")+string(c.Body()), mac) {
return c.Status(http.StatusForbidden).SendString("hmac verification failed")
}
f, err := c.FormFile("file")
if err != nil {
return c.Status(http.StatusBadRequest).SendString("file missing")
}
src, err := f.Open()
if err != nil {
return c.Status(http.StatusInternalServerError).SendString("cannot open file")
}
defer src.Close()
rz, err := zip.NewReader(src, f.Size)
if err != nil {
return c.Status(http.StatusBadRequest).SendString("invalid zip")
}
const targetDir = "./uploads"
for _, zf := range rz.File {
path, err := safePath(zf.Name, targetDir)
if err != nil {
return c.Status(http.StatusBadRequest).SendString("invalid entry path")
}
if err := extractFile(zf, path); err != nil {
return c.Status(http.StatusInternalServerError).SendString("extraction error")
}
}
return c.SendStatus(http.StatusOK)
}
func safePath(name, dest string) (string, error) {
clean := filepath.Clean(name)
if clean == ".." || strings.HasPrefix(clean, "../") || strings.Contains(clean, "..\\") {
return "", &fiber.StatusError{Code: http.StatusBadRequest, Message: "invalid path"}
}
full := filepath.Join(dest, clean)
if !strings.HasPrefix(full, filepath.Clean(dest)+string(filepath.Separator)) && full != filepath.Clean(dest) {
return "", &fiber.StatusError{Code: http.StatusBadRequest, Message: "path traversal detected"}
}
return full, nil
}
func extractFile(f *zip.File, path string) error {
if f.FileInfo().IsDir() {
return os.MkdirAll(path, 0o755)
}
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, rc)
return err
}
These examples show how to integrate Hmac Signatures for request authentication while applying strict path validation. The remediation ensures that even with a valid signature, archive entries are normalized, checked against directory traversal patterns, and constrained to an allowed base directory. This aligns with the scanner’s checks for unsafe consumption and property authorization, ensuring that cryptographic integrity does not replace file system safety.