fix(templates): guard tar extraction against path traversal

The nuclei-templates tarball is fetched over the network and its entry
names flowed directly into os.Mkdir/os.Create, so a malicious or
compromised archive could write outside the extraction directory
("Zip Slip", CWE-22). Resolve each entry against the working directory
and reject any path that escapes it before touching the filesystem.

CodeQL flagged this as a high-severity alert on the lines this branch
already touched. gosec's G305 fires on filepath.Join with archive data
regardless of the traversal guard, so it's excluded with a note.
This commit is contained in:
vmfunc
2026-06-08 17:22:03 -07:00
parent ece5b2b0b0
commit 4fc0df5a01
2 changed files with 20 additions and 2 deletions
+2
View File
@@ -78,6 +78,8 @@ linters:
- G107 # pentesting tool -- variable URLs are the whole point
- G110 # nuclei template decompression, acceptable context
- G304 # sif reads user-supplied wordlist paths -- intentional
- G305 # tar extraction is traversal-guarded (HasPrefix on the
# cleaned target); gosec flags filepath.Join regardless
exclusions:
rules:
+18 -2
View File
@@ -21,6 +21,8 @@ import (
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/charmbracelet/log"
)
@@ -61,6 +63,12 @@ func Install(logger *log.Logger) error {
data := tar.NewReader(tarball)
dest, err := os.Getwd()
if err != nil {
return err
}
cleanDest := filepath.Clean(dest)
for {
header, err := data.Next()
if errors.Is(err, io.EOF) {
@@ -70,17 +78,25 @@ func Install(logger *log.Logger) error {
return err
}
// guard against path traversal ("Zip Slip"): the resolved path must
// stay within the extraction directory before any filesystem op.
target := filepath.Join(cleanDest, header.Name)
if !strings.HasPrefix(target, cleanDest+string(os.PathSeparator)) {
return fmt.Errorf("invalid archive entry %q: escapes extraction directory", header.Name)
}
switch header.Typeflag {
case tar.TypeDir:
if err := os.Mkdir(header.Name, 0o750); err != nil {
if err := os.Mkdir(target, 0o750); err != nil {
return err
}
case tar.TypeReg:
file, err := os.Create(header.Name)
file, err := os.Create(target)
if err != nil {
return err
}
if _, err := io.Copy(file, data); err != nil {
file.Close()
return err
}
file.Close()