main
Raw Download raw file
  1package cmd
  2
  3import (
  4	"bufio"
  5	"context"
  6	"encoding/json"
  7	"fmt"
  8	"mysh/pkg/mythic"
  9	"os"
 10	"path/filepath"
 11	"strings"
 12	"time"
 13
 14	"github.com/spf13/cobra"
 15)
 16
 17var (
 18	uploadPayloadID  int
 19	uploadCallbackID int
 20	uploadPath       string
 21	uploadLocalFile  string
 22)
 23
 24var uploadCmd = &cobra.Command{
 25	Use:   "upload",
 26	Short: "Upload a file to a callback",
 27	Long:  "Upload a payload or local file to a specified callback location with confirmation prompt. Either --payload or --local must be specified.",
 28	RunE:  runUpload,
 29}
 30
 31func init() {
 32	rootCmd.AddCommand(uploadCmd)
 33	uploadCmd.Flags().IntVar(&uploadPayloadID, "payload", 0, "Payload ID to upload")
 34	uploadCmd.Flags().StringVar(&uploadLocalFile, "local", "", "Local file path to upload")
 35	uploadCmd.Flags().IntVar(&uploadCallbackID, "callback", 0, "Callback ID to upload to")
 36	uploadCmd.Flags().StringVar(&uploadPath, "path", "", "Remote path where to upload the file")
 37
 38	uploadCmd.MarkFlagRequired("callback")
 39	uploadCmd.MarkFlagRequired("path")
 40}
 41
 42type UploadParams struct {
 43	RemotePath string `json:"remote_path"`
 44	File       string `json:"file"`
 45	FileName   string `json:"file_name"`
 46	Host       string `json:"host"`
 47}
 48
 49func runUpload(cmd *cobra.Command, args []string) error {
 50	if err := validateConfig(); err != nil {
 51		return err
 52	}
 53
 54	// Validate that exactly one of --payload or --local is specified
 55	if (uploadPayloadID == 0 && uploadLocalFile == "") || (uploadPayloadID != 0 && uploadLocalFile != "") {
 56		return fmt.Errorf("exactly one of --payload or --local must be specified")
 57	}
 58
 59	client := mythic.NewClient(mythicURL, token, insecure, socksProxy)
 60	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 61	defer cancel()
 62
 63	// Get callback information
 64	callbacks, err := client.GetActiveCallbacks(ctx)
 65	if err != nil {
 66		return fmt.Errorf("failed to get callbacks: %w", err)
 67	}
 68
 69	var targetCallback *mythic.Callback
 70	for _, callback := range callbacks {
 71		if callback.DisplayID == uploadCallbackID {
 72			targetCallback = &callback
 73			break
 74		}
 75	}
 76
 77	if targetCallback == nil {
 78		return fmt.Errorf("callback %d not found or not active", uploadCallbackID)
 79	}
 80
 81	var uploadParams UploadParams
 82	var sourceDescription string
 83
 84	if uploadPayloadID != 0 {
 85		// Payload mode
 86		payloads, err := client.GetPayloads(ctx)
 87		if err != nil {
 88			return fmt.Errorf("failed to get payloads: %w", err)
 89		}
 90
 91		var targetPayload *mythic.Payload
 92		for _, payload := range payloads {
 93			if payload.ID == uploadPayloadID {
 94				targetPayload = &payload
 95				break
 96			}
 97		}
 98
 99		if targetPayload == nil {
100			return fmt.Errorf("payload with ID %d not found", uploadPayloadID)
101		}
102
103		uploadParams = UploadParams{
104			RemotePath: uploadPath,
105			File:       targetPayload.AgentFileID,
106			FileName:   targetPayload.Filename,
107			Host:       targetCallback.Host,
108		}
109
110		sourceDescription = fmt.Sprintf("Payload: %s (Type: %s, ID: %d)", targetPayload.Filename, targetPayload.PayloadTypeName, targetPayload.ID)
111	} else {
112		// Local file mode
113		// Check if local file exists
114		if _, err := os.Stat(uploadLocalFile); os.IsNotExist(err) {
115			return fmt.Errorf("local file does not exist: %s", uploadLocalFile)
116		}
117
118		// For local files, we need to create the file reference in Mythic first
119		// This is a simplified approach - in reality, we'd need to upload the file content
120		filename := filepath.Base(uploadLocalFile)
121
122		uploadParams = UploadParams{
123			RemotePath: uploadPath,
124			File:       uploadLocalFile, // Using local path as placeholder
125			FileName:   filename,
126			Host:       targetCallback.Host,
127		}
128
129		sourceDescription = fmt.Sprintf("Local file: %s", uploadLocalFile)
130	}
131
132	// Display confirmation information
133	fmt.Printf("Upload Details:\n")
134	fmt.Printf("===============\n")
135	fmt.Printf("%s\n", sourceDescription)
136	fmt.Printf("Target:  %s@%s (Callback: %d)\n", targetCallback.User, targetCallback.Host, targetCallback.DisplayID)
137	fmt.Printf("Path:    %s\n", uploadPath)
138	fmt.Printf("\nProceed with upload? [y/N]: ")
139
140	// Get user confirmation
141	reader := bufio.NewReader(os.Stdin)
142	response, err := reader.ReadString('\n')
143	if err != nil {
144		return fmt.Errorf("failed to read input: %w", err)
145	}
146
147	response = strings.TrimSpace(strings.ToLower(response))
148	if response != "y" && response != "yes" {
149		fmt.Println("Upload cancelled.")
150		return nil
151	}
152
153	// Convert to JSON
154	paramsJSON, err := json.Marshal(uploadParams)
155	if err != nil {
156		return fmt.Errorf("failed to marshal upload parameters: %w", err)
157	}
158
159	fmt.Printf("\nCreating upload task...\n")
160
161	// Create the upload task
162	task, err := client.CreateTask(ctx, targetCallback.ID, "upload", string(paramsJSON))
163	if err != nil {
164		return fmt.Errorf("failed to create upload task: %w", err)
165	}
166
167	fmt.Printf("Upload task %d created successfully\n", task.DisplayID)
168	fmt.Printf("Task parameters: %s\n", string(paramsJSON))
169
170	if uploadLocalFile != "" {
171		fmt.Printf("\nNote: Local file uploads require the file to be manually transferred to Mythic first.\n")
172		fmt.Printf("This command creates the upload task but does not transfer the file content.\n")
173	}
174
175	return nil
176}