main
1package cmd
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "mysh/pkg/cache"
8 "mysh/pkg/mythic"
9 "mysh/pkg/mythic/api"
10 "strconv"
11 "strings"
12 "time"
13
14 "github.com/spf13/cobra"
15)
16
17var (
18 taskViewRawOutput bool
19 taskViewDetails bool
20)
21
22var taskViewCmd = &cobra.Command{
23 Use: "task-view <task_id>",
24 Aliases: []string{"tv"},
25 Short: "Show details for a specific task",
26 Long: "Display detailed information and response for a specific task by its ID.",
27 Args: cobra.ExactArgs(1),
28 RunE: runTaskView,
29}
30
31func init() {
32 rootCmd.AddCommand(taskViewCmd)
33 taskViewCmd.Flags().BoolVarP(&taskViewRawOutput, "raw", "r", false, "Output only raw response bytes")
34 taskViewCmd.Flags().BoolVarP(&taskViewDetails, "details", "d", false, "Show only task details (no response)")
35}
36
37// formatJSONForDisplay formats JSON with proper indentation for display
38func formatJSONForDisplay(jsonStr, indent string) string {
39 if jsonStr == "" {
40 return ""
41 }
42
43 // Try to parse and format as JSON
44 var parsed interface{}
45 if err := json.Unmarshal([]byte(jsonStr), &parsed); err != nil {
46 // If it's not valid JSON, return as-is with indent
47 return indent + jsonStr
48 }
49
50 // Format with indentation
51 formatted, err := json.MarshalIndent(parsed, indent, " ")
52 if err != nil {
53 // If formatting fails, return original with indent
54 return indent + jsonStr
55 }
56
57 return string(formatted)
58}
59
60func runTaskView(cmd *cobra.Command, args []string) error {
61 if err := validateConfig(); err != nil {
62 return err
63 }
64
65 taskID, err := strconv.Atoi(args[0])
66 if err != nil {
67 return fmt.Errorf("invalid task ID: %s", args[0])
68 }
69
70 client := mythic.NewClient(mythicURL, token, insecure, socksProxy)
71 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
72 defer cancel()
73
74 // Initialize cache
75 taskCache, err := cache.New(mythicURL)
76 if err != nil {
77 // If cache initialization fails, continue without caching
78 taskCache = nil
79 }
80
81 var task *mythic.Task
82
83 // Try to get from cache first if available
84 if taskCache != nil {
85 if cachedTask, found := taskCache.GetCachedTask(taskID, mythicURL); found {
86 task = cachedTask
87 }
88 }
89
90 // If not in cache, fetch from server
91 if task == nil {
92 task, err = client.GetTaskResponse(ctx, taskID)
93 if err != nil {
94 return fmt.Errorf("failed to get task %d: %w", taskID, err)
95 }
96
97 // Cache the result if task is completed and cache is available
98 if taskCache != nil {
99 taskCache.CacheTask(task, mythicURL)
100 }
101 }
102
103 // If raw output requested, only print the response
104 if taskViewRawOutput {
105 if task.Response != "" {
106 fmt.Print(task.Response)
107 // Ensure output ends with a newline
108 if !strings.HasSuffix(task.Response, "\n") {
109 fmt.Print("\n")
110 }
111 }
112 return nil
113 }
114
115 // Get callback information for enhanced details
116 var callback *mythic.Callback
117 if task.CallbackID > 0 {
118 callback, _ = api.FindActiveCallback(ctx, client, task.CallbackID)
119 }
120
121 // Show enhanced task details header
122 fmt.Printf("Task Details (ID: %d)\n", task.DisplayID)
123 fmt.Println(strings.Repeat("=", 60))
124
125 // Basic task info
126 if task.Command != "" {
127 fmt.Printf("Command: %s\n", task.Command)
128 } else {
129 fmt.Printf("Command: (not available)\n")
130 }
131 fmt.Printf("Status: %s\n", task.Status)
132 fmt.Printf("Completed: %t\n", task.Completed)
133
134 // Operator and agent info
135 if task.Operator.Username != "" {
136 fmt.Printf("Operator: %s\n", task.Operator.Username)
137 }
138 if task.AgentTaskID != "" {
139 fmt.Printf("Agent Task ID: %s\n", task.AgentTaskID)
140 }
141
142 // Parameters section with proper JSON formatting
143 hasParams := task.OriginalParams != "" || task.DisplayParams != "" || task.Params != ""
144 if hasParams {
145 fmt.Printf("\nAgent Parameters:\n")
146
147 // Show original parameters (JSON) if available
148 if task.OriginalParams != "" {
149 fmt.Printf(" Original (JSON):\n %s\n", task.OriginalParams)
150 }
151
152 // Show display parameters if different from original
153 if task.DisplayParams != "" && task.DisplayParams != task.OriginalParams {
154 fmt.Printf(" Display Format:\n %s\n", task.DisplayParams)
155 }
156
157 // Show processed parameters if different from both above
158 if task.Params != "" && task.Params != task.DisplayParams && task.Params != task.OriginalParams {
159 fmt.Printf(" Processed:\n %s\n", task.Params)
160 }
161
162 // If only basic params available, show those
163 if task.OriginalParams == "" && task.DisplayParams == "" && task.Params != "" {
164 fmt.Printf(" Parameters:\n %s\n", task.Params)
165 }
166
167 // If no parameters at all
168 if !hasParams {
169 fmt.Printf(" (No parameters)\n")
170 }
171 }
172
173 // Task metadata
174 fmt.Printf("\nTask Metadata:\n")
175 if task.ParameterGroupName != "" {
176 fmt.Printf(" Parameter Group: %s\n", task.ParameterGroupName)
177 }
178 if task.TaskingLocation != "" {
179 fmt.Printf(" Tasking Location: %s\n", task.TaskingLocation)
180 }
181 if task.IsInteractiveTask {
182 fmt.Printf(" Interactive Task: Yes (Type: %d)\n", task.InteractiveTaskType)
183 }
184 if task.ParentTaskID > 0 {
185 fmt.Printf(" Parent Task: %d\n", task.ParentTaskID)
186 }
187 if task.Token.TokenID > 0 {
188 fmt.Printf(" Token ID: %d\n", task.Token.TokenID)
189 }
190
191 // Callback information
192 fmt.Printf("\nCallback Information:\n")
193 if callback != nil {
194 fmt.Printf(" Callback ID: %d\n", task.CallbackID)
195 fmt.Printf(" Host/User: %s@%s\n", callback.User, callback.Host)
196 fmt.Printf(" Process: %s (PID: %d)\n", callback.ProcessName, callback.PID)
197 if callback.Description != "" {
198 fmt.Printf(" Description: %s\n", callback.Description)
199 }
200 agentType := callback.Payload.PayloadType.Name
201 if agentType != "" {
202 fmt.Printf(" Agent Type: %s\n", agentType)
203 }
204 } else {
205 fmt.Printf(" Callback ID: %d\n", task.CallbackID)
206 }
207
208 // Timing information
209 fmt.Printf("\nTiming:\n")
210 if task.Timestamp != "" {
211 if t, err := time.Parse(time.RFC3339, task.Timestamp); err == nil {
212 fmt.Printf(" Created: %s\n", t.Format("2006-01-02 15:04:05 MST"))
213 } else {
214 fmt.Printf(" Created: %s\n", task.Timestamp)
215 }
216 }
217
218 // Technical details
219 fmt.Printf("\nTechnical Details:\n")
220 fmt.Printf(" Internal ID: %d\n", task.ID)
221
222 // If --details flag is set, only show the header
223 if taskViewDetails {
224 return nil
225 }
226
227 // Show response section
228 fmt.Println("\nResponse:")
229 fmt.Println(strings.Repeat("-", 40))
230 if task.Response != "" {
231 fmt.Println(task.Response)
232 } else {
233 fmt.Println("(No response yet)")
234 }
235
236 return nil
237}