Skip to content

Commit

Permalink
Adding the ability to upload directories
Browse files Browse the repository at this point in the history
  • Loading branch information
RafBishopFox committed Oct 4, 2023
1 parent 5d40d41 commit efe747b
Show file tree
Hide file tree
Showing 8 changed files with 1,545 additions and 1,033 deletions.
361 changes: 341 additions & 20 deletions client/command/filesystem/upload.go

Large diffs are not rendered by default.

21 changes: 20 additions & 1 deletion client/command/help/long-help.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,26 @@ If you need to match a special character (*, ?, '-', '[', ']', '\\'), place '\\'
On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`

uploadHelp = `[[.Bold]]Command:[[.Normal]] upload [local src] <remote dst>
[[.Bold]]About:[[.Normal]] Upload a file to the remote system.`
[[.Bold]]About:[[.Normal]] Upload a file or directory to the remote system.
[[.Bold]][[.Underline]]Filters[[.Normal]]
Filters are a way to limit uploads to file names matching given criteria. Filters DO NOT apply to directory names.
Filters are specified after the path. A blank path will filter on names in the current directory. For example:
upload /etc/*.conf will upload all files from /etc whose names end in .conf. /etc/ is the path, *.conf is the filter.
Uploads can be filtered using the following patterns:
'*': Wildcard, matches any sequence of non-path separators (slashes)
Example: n*.txt will match all file names starting with n and ending with .txt
'?': Single character wildcard, matches a single non-path separator (slashes)
Example: s?iver will match all file names starting with s followed by any non-separator character and ending with iver.
'[{range}]': Match a range of characters. Ranges are specified with '-'. This is usually combined with other patterns. Ranges can be negated with '^'.
Example: [a-c] will match the characters a, b, and c. [a-c]* will match all file names that start with a, b, or c.
^[r-u] will match all characters except r, s, t, and u.
If you need to match a special character (*, ?, '-', '[', ']', '\\'), place '\\' in front of it (example: \\?).
On Windows, escaping is disabled. Instead, '\\' is treated as path separator.`

procdumpHelp = `[[.Bold]]Command:[[.Normal]] procdump [pid]
[[.Bold]]About:[[.Normal]] Dumps the process memory given a process identifier (pid)`
Expand Down
5 changes: 4 additions & 1 deletion client/command/sliver.go
Original file line number Diff line number Diff line change
Expand Up @@ -941,7 +941,7 @@ func SliverCommands(con *client.SliverConsoleClient) console.Commands {

uploadCmd := &cobra.Command{
Use: consts.UploadStr,
Short: "Upload a file",
Short: "Upload a file or directory",
Long: help.GetHelpFor([]string{consts.UploadStr}),
Args: cobra.RangeArgs(1, 2),
Run: func(cmd *cobra.Command, args []string) {
Expand All @@ -952,6 +952,9 @@ func SliverCommands(con *client.SliverConsoleClient) console.Commands {
sliver.AddCommand(uploadCmd)
Flags("", false, uploadCmd, func(f *pflag.FlagSet) {
f.BoolP("ioc", "i", false, "track uploaded file as an ioc")
f.BoolP("recurse", "r", false, "recursively upload a directory")
f.BoolP("overwrite", "o", false, "overwrite files that exist in the destination")
f.BoolP("preserve", "p", false, "preserve directory structure when uploading a directory")
f.Int64P("timeout", "t", defaultTimeout, "grpc timeout in seconds")
})
carapace.Gen(uploadCmd).PositionalCompletion(
Expand Down
173 changes: 150 additions & 23 deletions implant/sliver/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,69 @@ func downloadHandler(data []byte, resp RPCResponse) {
resp(data, err)
}

func extractFiles(data []byte, path string, overwrite bool) (int, int, error) {
reader := tar.NewReader(bytes.NewReader(data))
filesSkipped := 0
filesWritten := 0

for {
header, err := reader.Next()

Check failure

Code scanning / CodeQL

Arbitrary file access during archive extraction ("Zip Slip") High

Unsanitized archive entry, which may contain '..', is used in a
file system operation
.
Unsanitized archive entry, which may contain '..', is used in a
file system operation
.
Unsanitized archive entry, which may contain '..', is used in a
file system operation
.
Unsanitized archive entry, which may contain '..', is used in a
file system operation
.
Unsanitized archive entry, which may contain '..', is used in a
file system operation
.
switch {
case err == io.EOF:
// Then we are done
return filesWritten, filesSkipped, nil
case err != nil:
// We should return the up to the caller
return filesWritten, filesSkipped, err
case header == nil:
// Just in case the error is nil, skip it
continue
}
fileName := filepath.Join(path, header.Name)

_, err = os.Stat(fileName)

// Take different actions based on whether this header entry is a directory or a file
switch header.Typeflag {
case tar.TypeDir:
if err != nil {
if err := os.MkdirAll(fileName, 0750); err != nil {
return filesWritten, filesSkipped, err
}
}
case tar.TypeReg:
if err == nil {
// Then the file exists
if !overwrite {
// If we are not overwriting files, then skip this entry
filesSkipped += 1
continue
}
}

// Check to make sure the destination directory exists, and if it does not, create it
destinationDir := filepath.Dir(fileName)
if _, err = os.Stat(destinationDir); errors.Is(err, fs.ErrNotExist) {
if err = os.MkdirAll(destinationDir, 0750); err != nil {
return filesWritten, filesSkipped, err
}
}
file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
file.Close()
return filesWritten, filesSkipped, err
}

if _, err := io.Copy(file, reader); err != nil {
file.Close()
return filesWritten, filesSkipped, err
}
file.Close()
filesWritten += 1
}
}
}

func uploadHandler(data []byte, resp RPCResponse) {
uploadReq := &sliverpb.UploadReq{}
err := proto.Unmarshal(data, uploadReq)
Expand All @@ -552,47 +615,111 @@ func uploadHandler(data []byte, resp RPCResponse) {
log.Printf("upload path error: %v", err)
// {{end}}
resp([]byte{}, err)
return
}

// Process Upload
upload := &sliverpb.Upload{Path: uploadPath}
uploadPathInfo, err := os.Stat(uploadPath)
if err != nil && !os.IsNotExist(err) {
upload.Response = &commonpb.Response{
Err: fmt.Sprintf("%v", err),
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
upload.Response = &commonpb.Response{
Err: fmt.Sprintf("%v", err),
}
} else if errors.Is(err, fs.ErrNotExist) {
if uploadReq.IsDirectory {
// We need to make a directory for the files
err = os.MkdirAll(uploadPath, 0750)
if err != nil && !errors.Is(err, fs.ErrExist) {
upload.Response = &commonpb.Response{
Err: fmt.Sprintf("%v", err),
}
}
} else {
// If the file does not exist, that is fine because we will create it.
err = nil
}
}
}

if !os.IsNotExist(err) && uploadPathInfo.IsDir() {
} else if uploadPathInfo.IsDir() {
if !strings.HasSuffix(uploadPath, string(os.PathSeparator)) {
uploadPath += string(os.PathSeparator)
upload.Path = uploadPath
}
if !uploadReq.IsDirectory {
uploadPath += uploadReq.FileName
uploadPathInfo, err = os.Stat(uploadPath)
upload.Path = uploadPath
// We will deal with any error in a bit.
if err != nil && errors.Is(err, fs.ErrNotExist) {
// If the file does not exist, that is fine because we will create it.
err = nil
}
}
uploadPath += uploadReq.FileName
}

f, err := os.Create(uploadPath)
if err != nil {
if uploadPathInfo != nil && !uploadPathInfo.IsDir() && !uploadReq.Overwrite {
// Then we are trying to overwrite a file that exists and
// the overwrite flag was not specified
err = fmt.Errorf("%s exists, but the overwrite flag was not set", uploadPath)
upload.Response = &commonpb.Response{
Err: fmt.Sprintf("%v", err),
}
}

// If we have not resolved the error by now, then bail.
if err != nil {
data, _ = proto.Marshal(upload)
resp(data, err)
return
}

var uploadData []byte

if uploadReq.Encoder == "gzip" {
uploadData, err = gzipRead(uploadReq.Data)
} else {
// Create file, write data to file system
defer f.Close()
var uploadData []byte
var err error
if uploadReq.Encoder == "gzip" {
uploadData, err = gzipRead(uploadReq.Data)
} else {
uploadData = uploadReq.Data
uploadData = uploadReq.Data
}
// Check for decode errors
if err != nil {
upload.Response = &commonpb.Response{
Err: fmt.Sprintf("%v", err),
}
// Check for decode errors
if err != nil {
upload.Response = &commonpb.Response{
Err: fmt.Sprintf("%v", err),
} else {
if uploadReq.IsDirectory {
filesWritten, filesSkipped, err := extractFiles(uploadData, uploadPath, uploadReq.Overwrite)
if err != nil {
writtenQualifer := "s"
if filesWritten == 1 {
writtenQualifer = ""
}
skippedQualifier := "s"
if filesSkipped == 1 {
skippedQualifier = ""
}
upload.Response = &commonpb.Response{
Err: fmt.Sprintf("%d file%s written, %d file%s skipped: %v", filesWritten, writtenQualifer, filesSkipped, skippedQualifier, err),
}
upload.WrittenFiles = int32(filesWritten)
upload.UnwriteableFiles = int32(filesSkipped)
}
upload.WrittenFiles = int32(filesWritten)
upload.UnwriteableFiles = int32(filesSkipped)
} else {
f.Write(uploadData)
f, err := os.Create(uploadPath)
if err != nil {
upload.Response = &commonpb.Response{
Err: fmt.Sprintf("%v", err),
}
upload.UnwriteableFiles = 1
upload.WrittenFiles = 0
} else {
// Create file, write data to file system
f.Write(uploadData)
f.Close()
upload.WrittenFiles = 1
upload.UnwriteableFiles = 0
}
}
}

Expand Down Expand Up @@ -824,7 +951,7 @@ func gzipWrite(w io.Writer, data []byte) error {
}

func gzipRead(data []byte) ([]byte, error) {
bytes.NewReader(data)
//bytes.NewReader(data)
reader, err := gzip.NewReader(bytes.NewReader(data))
if err != nil {
return nil, err
Expand Down
4 changes: 2 additions & 2 deletions protobuf/rpcpb/services.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion protobuf/rpcpb/services_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit efe747b

Please sign in to comment.