github.com/facebookincubator/ttpforge@v1.0.13-0.20240405153150-5ae801628835/pkg/blocks/copypath.go (about)

     1  /*
     2  Copyright © 2023-present, Meta Platforms, Inc. and affiliates
     3  Permission is hereby granted, free of charge, to any person obtaining a copy
     4  of this software and associated documentation files (the "Software"), to deal
     5  in the Software without restriction, including without limitation the rights
     6  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  copies of the Software, and to permit persons to whom the Software is
     8  furnished to do so, subject to the following conditions:
     9  The above copyright notice and this permission notice shall be included in
    10  all copies or substantial portions of the Software.
    11  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    12  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    13  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    14  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    15  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    16  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    17  THE SOFTWARE.
    18  */
    19  
    20  package blocks
    21  
    22  import (
    23  	"fmt"
    24  
    25  	"github.com/facebookincubator/ttpforge/pkg/logging"
    26  	"github.com/otiai10/copy"
    27  	"github.com/spf13/afero"
    28  )
    29  
    30  // CopyPathStep creates a new file and populates it
    31  // with the specified contents from an existing path.
    32  // Its intended use is simulating malicious file copies
    33  // via a C2, where there is no corresponding shell history
    34  // telemetry.
    35  type CopyPathStep struct {
    36  	actionDefaults `yaml:",inline"`
    37  	Source         string   `yaml:"copy_path,omitempty"`
    38  	Destination    string   `yaml:"to,omitempty"`
    39  	Recursive      bool     `yaml:"recursive,omitempty"`
    40  	Overwrite      bool     `yaml:"overwrite,omitempty"`
    41  	Mode           int      `yaml:"mode,omitempty"`
    42  	FileSystem     afero.Fs `yaml:"-,omitempty"`
    43  }
    44  
    45  // NewCopyPathStep creates a new CopyPathStep instance and returns a pointer to it.
    46  func NewCopyPathStep() *CopyPathStep {
    47  	return &CopyPathStep{}
    48  }
    49  
    50  // IsNil checks if the step is nil or empty and returns a boolean value.
    51  func (s *CopyPathStep) IsNil() bool {
    52  	switch {
    53  	case s.Source == "":
    54  		return true
    55  	case s.Destination == "":
    56  		return true
    57  	default:
    58  		return false
    59  	}
    60  }
    61  
    62  // Validate validates the step, checking for the necessary attributes and dependencies
    63  func (s *CopyPathStep) Validate(_ TTPExecutionContext) error {
    64  	if s.Source == "" {
    65  		return fmt.Errorf("src field cannot be empty")
    66  	}
    67  	if s.Destination == "" {
    68  		return fmt.Errorf("dest field cannot be empty")
    69  	}
    70  	return nil
    71  }
    72  
    73  // Execute runs the step and returns an error if one occurs.
    74  func (s *CopyPathStep) Execute(_ TTPExecutionContext) (*ActResult, error) {
    75  	logging.L().Infof("Copying file(s) from %v to %v", s.Source, s.Destination)
    76  	fsys := s.FileSystem
    77  	if fsys == nil {
    78  		fsys = afero.NewOsFs()
    79  	}
    80  
    81  	// check if source exists.
    82  	sourceExists, err := afero.Exists(fsys, s.Source)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	// if source does not exist.
    88  	if !sourceExists {
    89  		return nil, fmt.Errorf("source %v does not exist", s.Source)
    90  	}
    91  
    92  	// if source is a directory but recursive is false
    93  	srcInfo, err := fsys.Stat(s.Source)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	if srcInfo.IsDir() && !s.Recursive {
    98  		return nil, fmt.Errorf("source %v is a directory, but the recursive flag is set to false", s.Source)
    99  	}
   100  
   101  	// check if destination exists.
   102  	destExists, err := afero.Exists(fsys, s.Destination)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	// if destination exits, return error if overwrite flag is not true.
   107  	if destExists && !s.Overwrite {
   108  		return nil, fmt.Errorf("dest %v already exists and overwrite was not set", s.Destination)
   109  	}
   110  
   111  	// use the default umask
   112  	// https://stackoverflow.com/questions/23842247/reading-default-filemode-when-using-os-o-create
   113  	mode := s.Mode
   114  	if mode == 0 {
   115  		mode = 0666
   116  	}
   117  
   118  	// Copy a file
   119  	err = copy.Copy(s.Source, s.Destination)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  
   124  	return &ActResult{}, nil
   125  }
   126  
   127  // GetDefaultCleanupAction will instruct the calling code
   128  // to remove the path created by this action
   129  func (s *CopyPathStep) GetDefaultCleanupAction() Action {
   130  	return &RemovePathAction{
   131  		Path: s.Destination,
   132  	}
   133  }
   134  
   135  // CanBeUsedInCompositeAction enables this action to be used in a composite action
   136  func (s *CopyPathStep) CanBeUsedInCompositeAction() bool {
   137  	return true
   138  }