main
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}