iotsploit-cli 0.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iotsploit_cli/__init__.py +1 -0
- iotsploit_cli/commands/__init__.py +1 -0
- iotsploit_cli/commands/base_commands.py +5 -0
- iotsploit_cli/commands/device_commands.py +536 -0
- iotsploit_cli/commands/django_commands.py +254 -0
- iotsploit_cli/commands/firmware_commands.py +213 -0
- iotsploit_cli/commands/linux_commands.py +35 -0
- iotsploit_cli/commands/network_commands.py +36 -0
- iotsploit_cli/commands/plugin_commands.py +249 -0
- iotsploit_cli/commands/system_commands.py +77 -0
- iotsploit_cli/commands/target_commands.py +246 -0
- iotsploit_cli/console.py +493 -0
- iotsploit_cli-0.0.6.dist-info/METADATA +67 -0
- iotsploit_cli-0.0.6.dist-info/RECORD +16 -0
- iotsploit_cli-0.0.6.dist-info/WHEEL +4 -0
- iotsploit_cli-0.0.6.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import cmd2
|
|
4
|
+
from cmd2 import ansi
|
|
5
|
+
from .base_commands import BaseCommands
|
|
6
|
+
from iotsploit_core.core.exploit_spec import ExploitResult
|
|
7
|
+
from iotsploit_django.tools.input_mgr import Input_Mgr
|
|
8
|
+
from iotsploit_django.adapters.django.plugins.models import Plugin
|
|
9
|
+
from iotsploit_django.adapters.django.plugins.models import PluginGroup, PluginGroupTree
|
|
10
|
+
from iotsploit_core.utils import iots_logger
|
|
11
|
+
|
|
12
|
+
logger = iots_logger.get_logger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PluginCommands(BaseCommands):
|
|
16
|
+
"""Plugin-related commands for the SAT Shell"""
|
|
17
|
+
|
|
18
|
+
@cmd2.with_category('Plugin Commands')
|
|
19
|
+
def do_list_plugins(self, arg):
|
|
20
|
+
'List all available plugins'
|
|
21
|
+
plugins = self.plugin_manager.list_plugins()
|
|
22
|
+
|
|
23
|
+
if plugins:
|
|
24
|
+
logger.info(ansi.style("Available plugins:", fg=ansi.Fg.CYAN))
|
|
25
|
+
for plugin in plugins:
|
|
26
|
+
logger.info(ansi.style(f" - {plugin}", fg=ansi.Fg.CYAN))
|
|
27
|
+
else:
|
|
28
|
+
logger.info(ansi.style("No plugins available.", fg=ansi.Fg.YELLOW))
|
|
29
|
+
|
|
30
|
+
do_lsp = do_list_plugins
|
|
31
|
+
|
|
32
|
+
@cmd2.with_category('Plugin Commands')
|
|
33
|
+
def do_execute_plugin(self, arg):
|
|
34
|
+
'Execute a specific plugin'
|
|
35
|
+
plugins = self.plugin_manager.list_plugins()
|
|
36
|
+
|
|
37
|
+
if not plugins:
|
|
38
|
+
logger.info(ansi.style("No plugins available to execute.", fg=ansi.Fg.YELLOW))
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
if not arg:
|
|
42
|
+
choice = Input_Mgr.Instance().single_choice(
|
|
43
|
+
"Please select a plugin to execute",
|
|
44
|
+
plugins
|
|
45
|
+
)
|
|
46
|
+
else:
|
|
47
|
+
choice = arg
|
|
48
|
+
|
|
49
|
+
if choice not in plugins:
|
|
50
|
+
logger.error(ansi.style(f"Plugin '{choice}' not found.", fg=ansi.Fg.RED))
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
logger.info(ansi.style(f"Executing plugin: {choice}", fg=ansi.Fg.CYAN))
|
|
54
|
+
try:
|
|
55
|
+
# Get the plugin instance to access its parameters
|
|
56
|
+
plugin_instance = self.plugin_manager.get_plugin(choice)
|
|
57
|
+
if not plugin_instance:
|
|
58
|
+
logger.error(ansi.style(f"Could not get plugin instance for '{choice}'", fg=ansi.Fg.RED))
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
# Get plugin info with parameters
|
|
62
|
+
plugin_info = plugin_instance.get_info()
|
|
63
|
+
plugin_params = plugin_info.get('Parameters', {})
|
|
64
|
+
|
|
65
|
+
# Get current target from target manager
|
|
66
|
+
target_manager = self.target_manager
|
|
67
|
+
current_target = target_manager.get_current_target()
|
|
68
|
+
|
|
69
|
+
# Prepare target dictionary
|
|
70
|
+
target_dict = {}
|
|
71
|
+
|
|
72
|
+
# If we have a target, include its properties
|
|
73
|
+
if current_target:
|
|
74
|
+
# Add target properties to target dictionary
|
|
75
|
+
target_dict = current_target.get_info() if hasattr(current_target, 'get_info') else {}
|
|
76
|
+
|
|
77
|
+
# Prompt for required parameters that are not in the target
|
|
78
|
+
for param_name, param_info in plugin_params.items():
|
|
79
|
+
if param_name not in target_dict and param_info.get('required', False):
|
|
80
|
+
param_type = param_info.get('type', 'str')
|
|
81
|
+
description = param_info.get('description', f"Enter {param_name}")
|
|
82
|
+
default = param_info.get('default')
|
|
83
|
+
validation = param_info.get('validation', {})
|
|
84
|
+
|
|
85
|
+
if param_type == 'str':
|
|
86
|
+
if 'choices' in validation:
|
|
87
|
+
# Use single_choice for string with choices
|
|
88
|
+
value = Input_Mgr.Instance().single_choice(
|
|
89
|
+
f"{description} (Choose one)",
|
|
90
|
+
validation['choices']
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
# Regular string input
|
|
94
|
+
value = Input_Mgr.Instance().string_input(description)
|
|
95
|
+
elif param_type == 'int':
|
|
96
|
+
# Integer input with optional min/max validation
|
|
97
|
+
min_val = validation.get('min')
|
|
98
|
+
max_val = validation.get('max')
|
|
99
|
+
value = Input_Mgr.Instance().int_input(
|
|
100
|
+
description,
|
|
101
|
+
min_val=min_val,
|
|
102
|
+
max_val=max_val
|
|
103
|
+
)
|
|
104
|
+
elif param_type == 'bool':
|
|
105
|
+
# Boolean input
|
|
106
|
+
value = Input_Mgr.Instance().yes_no_input(
|
|
107
|
+
description,
|
|
108
|
+
default=default if default is not None else True
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
# Default to string for unknown types
|
|
112
|
+
value = Input_Mgr.Instance().string_input(description)
|
|
113
|
+
|
|
114
|
+
# Add to target dict
|
|
115
|
+
target_dict[param_name] = value
|
|
116
|
+
|
|
117
|
+
logger.debug(f"Executing plugin with target configuration: {target_dict}")
|
|
118
|
+
|
|
119
|
+
# Now execute the plugin with our target dictionary
|
|
120
|
+
result = self.plugin_manager.execute_plugin(choice, target=target_dict)
|
|
121
|
+
|
|
122
|
+
# Check if this is an async execution
|
|
123
|
+
if isinstance(result, dict) and result.get('execution_type') == 'async':
|
|
124
|
+
task_id = result.get('task_id')
|
|
125
|
+
logger.info(ansi.style(f"Plugin running asynchronously with task ID: {task_id}", fg=ansi.Fg.CYAN))
|
|
126
|
+
|
|
127
|
+
# Ask user if they want to wait for results
|
|
128
|
+
wait_for_results = Input_Mgr.Instance().yes_no_input(
|
|
129
|
+
"Do you want to wait for the asynchronous task to complete?",
|
|
130
|
+
default=True
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if wait_for_results:
|
|
134
|
+
# Import celery here to avoid circular imports
|
|
135
|
+
try:
|
|
136
|
+
from celery.result import AsyncResult
|
|
137
|
+
from iotsploit_django.tasks import celery_app
|
|
138
|
+
import time
|
|
139
|
+
|
|
140
|
+
task_result = AsyncResult(task_id, app=celery_app)
|
|
141
|
+
|
|
142
|
+
# Poll for results with a progress bar
|
|
143
|
+
progress = 0
|
|
144
|
+
start_time = time.time()
|
|
145
|
+
|
|
146
|
+
logger.info(ansi.style("Waiting for task to complete...", fg=ansi.Fg.CYAN))
|
|
147
|
+
|
|
148
|
+
while not task_result.ready():
|
|
149
|
+
# Try to get progress information
|
|
150
|
+
task_info = task_result.info
|
|
151
|
+
|
|
152
|
+
if isinstance(task_info, dict):
|
|
153
|
+
new_progress = task_info.get('progress', 0)
|
|
154
|
+
message = task_info.get('message', 'Processing...')
|
|
155
|
+
|
|
156
|
+
# Only update if progress has changed
|
|
157
|
+
if new_progress != progress:
|
|
158
|
+
progress = new_progress
|
|
159
|
+
# Print progress bar
|
|
160
|
+
bar_length = 50
|
|
161
|
+
filled_length = int(bar_length * progress / 100)
|
|
162
|
+
bar = '█' * filled_length + '-' * (bar_length - filled_length)
|
|
163
|
+
logger.info(f"Progress: [{bar}] {progress:.1f}% - {message}")
|
|
164
|
+
|
|
165
|
+
# Sleep briefly before checking again
|
|
166
|
+
time.sleep(0.5)
|
|
167
|
+
|
|
168
|
+
# Add a timeout to prevent infinite waiting
|
|
169
|
+
if time.time() - start_time > 300: # 5 minutes
|
|
170
|
+
logger.warning(ansi.style("Timeout waiting for task to complete", fg=ansi.Fg.YELLOW))
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
# Get final result
|
|
174
|
+
final_result = task_result.get(timeout=5) # 5 second timeout for final result
|
|
175
|
+
|
|
176
|
+
logger.info(ansi.style("Async plugin execution completed", fg=ansi.Fg.GREEN))
|
|
177
|
+
if isinstance(final_result, dict):
|
|
178
|
+
logger.info(ansi.style("Plugin execution result:", fg=ansi.Fg.GREEN))
|
|
179
|
+
for key, value in final_result.items():
|
|
180
|
+
logger.info(f"{key}: {value}")
|
|
181
|
+
else:
|
|
182
|
+
logger.info(f"Result: {final_result}")
|
|
183
|
+
|
|
184
|
+
except ImportError as e:
|
|
185
|
+
logger.error(ansi.style(f"Error importing Celery modules: {str(e)}", fg=ansi.Fg.RED))
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.error(ansi.style(f"Error getting async result: {str(e)}", fg=ansi.Fg.RED))
|
|
188
|
+
logger.debug("Detailed error:", exc_info=True)
|
|
189
|
+
|
|
190
|
+
# Display initial async info regardless
|
|
191
|
+
logger.info(ansi.style("Initial async task info:", fg=ansi.Fg.GREEN))
|
|
192
|
+
logger.info(str(result))
|
|
193
|
+
|
|
194
|
+
elif isinstance(result, ExploitResult):
|
|
195
|
+
logger.info(ansi.style("Plugin execution result:", fg=ansi.Fg.GREEN))
|
|
196
|
+
logger.info(f"Success: {result.success}")
|
|
197
|
+
logger.info(f"Message: {result.message}")
|
|
198
|
+
logger.info(f"Data: {result.data}")
|
|
199
|
+
else:
|
|
200
|
+
logger.info(ansi.style("Plugin execution result:", fg=ansi.Fg.GREEN))
|
|
201
|
+
logger.info(str(result))
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(ansi.style(f"Error executing plugin: {str(e)}", fg=ansi.Fg.RED))
|
|
204
|
+
logger.debug("Detailed error:", exc_info=True)
|
|
205
|
+
|
|
206
|
+
do_exec = do_execute_plugin
|
|
207
|
+
|
|
208
|
+
@cmd2.with_category('Plugin Commands')
|
|
209
|
+
def do_flash_plugins(self, arg):
|
|
210
|
+
'Refresh and reload all plugins from installed packages and entry points'
|
|
211
|
+
try:
|
|
212
|
+
logger.info(ansi.style("Starting plugin refresh...", fg=ansi.Fg.CYAN))
|
|
213
|
+
|
|
214
|
+
# Get current plugin count
|
|
215
|
+
initial_plugins = len(self.plugin_manager.list_plugins())
|
|
216
|
+
|
|
217
|
+
# Run auto-discovery
|
|
218
|
+
self.plugin_manager.auto_discover_plugins()
|
|
219
|
+
|
|
220
|
+
# Get new plugin count
|
|
221
|
+
final_plugins = len(self.plugin_manager.list_plugins())
|
|
222
|
+
|
|
223
|
+
# Calculate changes
|
|
224
|
+
if final_plugins > initial_plugins:
|
|
225
|
+
logger.info(ansi.style(
|
|
226
|
+
f"Plugin refresh complete! Added {final_plugins - initial_plugins} new plugins.",
|
|
227
|
+
fg=ansi.Fg.GREEN
|
|
228
|
+
))
|
|
229
|
+
elif final_plugins < initial_plugins:
|
|
230
|
+
logger.info(ansi.style(
|
|
231
|
+
f"Plugin refresh complete! Removed {initial_plugins - final_plugins} plugins.",
|
|
232
|
+
fg=ansi.Fg.YELLOW
|
|
233
|
+
))
|
|
234
|
+
else:
|
|
235
|
+
logger.info(ansi.style(
|
|
236
|
+
"Plugin refresh complete! No changes detected.",
|
|
237
|
+
fg=ansi.Fg.CYAN
|
|
238
|
+
))
|
|
239
|
+
|
|
240
|
+
# Display current plugins
|
|
241
|
+
logger.info(ansi.style("\nCurrent plugins:", fg=ansi.Fg.CYAN))
|
|
242
|
+
for plugin in self.plugin_manager.list_plugins():
|
|
243
|
+
logger.info(ansi.style(f" - {plugin}", fg=ansi.Fg.CYAN))
|
|
244
|
+
|
|
245
|
+
except Exception as e:
|
|
246
|
+
logger.error(ansi.style(f"Error refreshing plugins: {str(e)}", fg=ansi.Fg.RED))
|
|
247
|
+
logger.debug("Detailed error:", exc_info=True)
|
|
248
|
+
|
|
249
|
+
do_fp = do_flash_plugins
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import cmd2
|
|
4
|
+
from cmd2 import ansi
|
|
5
|
+
from .base_commands import BaseCommands
|
|
6
|
+
from iotsploit_core.core.exploit_spec import ExploitResult
|
|
7
|
+
from iotsploit_django.tools.input_mgr import Input_Mgr
|
|
8
|
+
from iotsploit_core.utils import iots_logger
|
|
9
|
+
|
|
10
|
+
logger = iots_logger.get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SystemCommands(BaseCommands):
|
|
14
|
+
"""System-related commands for the SAT Shell"""
|
|
15
|
+
|
|
16
|
+
@cmd2.with_category('System Commands')
|
|
17
|
+
def do_exploit(self, arg):
|
|
18
|
+
'Execute all plugins in the IotSploit System'
|
|
19
|
+
logger.info(ansi.style("Executing all plugins in the IotSploit System", fg=ansi.Fg.CYAN))
|
|
20
|
+
|
|
21
|
+
results = self.plugin_manager.exploit()
|
|
22
|
+
|
|
23
|
+
if not results:
|
|
24
|
+
logger.warning(ansi.style("No results returned from any plugins", fg=ansi.Fg.YELLOW))
|
|
25
|
+
else:
|
|
26
|
+
# Log the results
|
|
27
|
+
for plugin_name, result in results.items():
|
|
28
|
+
if result is None:
|
|
29
|
+
logger.warning(ansi.style(f"Plugin {plugin_name} returned no result", fg=ansi.Fg.YELLOW))
|
|
30
|
+
elif isinstance(result, ExploitResult):
|
|
31
|
+
logger.info(ansi.style(f"Plugin {plugin_name} execution result:", fg=ansi.Fg.GREEN))
|
|
32
|
+
logger.info(f"Success: {result.success}")
|
|
33
|
+
logger.info(f"Message: {result.message}")
|
|
34
|
+
logger.info(f"Data: {result.data}")
|
|
35
|
+
else:
|
|
36
|
+
logger.info(ansi.style(f"Plugin {plugin_name} execution result:", fg=ansi.Fg.GREEN))
|
|
37
|
+
logger.info(str(result))
|
|
38
|
+
|
|
39
|
+
logger.info(ansi.style("Exploit execution completed", fg=ansi.Fg.CYAN))
|
|
40
|
+
|
|
41
|
+
@cmd2.with_category('System Commands')
|
|
42
|
+
def do_exit(self, arg):
|
|
43
|
+
'Exit Console'
|
|
44
|
+
if self.django_server_process:
|
|
45
|
+
self.do_stop_server(arg)
|
|
46
|
+
self._cleanup_devices()
|
|
47
|
+
logger.info("IotSploit Shell Quit. ByeBye~")
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
@cmd2.with_category('System Commands')
|
|
51
|
+
def do_set_log_level(self, arg):
|
|
52
|
+
'Set the logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)'
|
|
53
|
+
valid_levels = ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']
|
|
54
|
+
|
|
55
|
+
if not arg:
|
|
56
|
+
# If no argument provided, let user select from valid levels
|
|
57
|
+
selected_level = Input_Mgr.Instance().single_choice(
|
|
58
|
+
"Select logging level",
|
|
59
|
+
valid_levels
|
|
60
|
+
)
|
|
61
|
+
else:
|
|
62
|
+
# If argument provided, validate it
|
|
63
|
+
selected_level = arg.upper()
|
|
64
|
+
if selected_level not in valid_levels:
|
|
65
|
+
logger.error(ansi.style(f"Invalid log level. Choose from: {', '.join(valid_levels)}", fg=ansi.Fg.RED))
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
# Set the log level via core logger (affects all core-managed loggers)
|
|
70
|
+
iots_logger.set_level(selected_level)
|
|
71
|
+
logger.info(ansi.style(f"Log level set to {selected_level}", fg=ansi.Fg.GREEN))
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error(ansi.style(f"Error setting log level: {str(e)}", fg=ansi.Fg.RED))
|
|
74
|
+
logger.debug("Detailed error:", exc_info=True)
|
|
75
|
+
|
|
76
|
+
# Add an alias for set_log_level
|
|
77
|
+
do_sll = do_set_log_level
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import cmd2
|
|
4
|
+
from cmd2 import ansi
|
|
5
|
+
from .base_commands import BaseCommands
|
|
6
|
+
from iotsploit_django.tools.input_mgr import Input_Mgr
|
|
7
|
+
from iotsploit_core.utils import iots_logger
|
|
8
|
+
|
|
9
|
+
logger = iots_logger.get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TargetCommands(BaseCommands):
|
|
13
|
+
"""Target-related commands for the SAT Shell"""
|
|
14
|
+
|
|
15
|
+
@cmd2.with_category('Target Commands')
|
|
16
|
+
def do_list_targets(self, arg):
|
|
17
|
+
'List all targets stored in the database'
|
|
18
|
+
try:
|
|
19
|
+
targets = self.target_manager.get_all_targets()
|
|
20
|
+
|
|
21
|
+
if not targets:
|
|
22
|
+
logger.info(ansi.style("No targets found in the database.", fg=ansi.Fg.YELLOW))
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
logger.info(ansi.style("Targets in the database:", fg=ansi.Fg.CYAN))
|
|
26
|
+
for target in targets:
|
|
27
|
+
logger.info(ansi.style(f" - ID: {target['target_id']}", fg=ansi.Fg.GREEN))
|
|
28
|
+
logger.info(f" Name: {target['name']}")
|
|
29
|
+
logger.info(f" Type: {target['type']}")
|
|
30
|
+
logger.info(f" Status: {target['status']}")
|
|
31
|
+
|
|
32
|
+
# All target types now have ip_address and location
|
|
33
|
+
logger.info(f" IP Address: {target.get('ip_address', 'N/A')}")
|
|
34
|
+
logger.info(f" Location: {target.get('location', 'N/A')}")
|
|
35
|
+
|
|
36
|
+
logger.info(f" Properties: {target['properties']}")
|
|
37
|
+
logger.info(" ---")
|
|
38
|
+
|
|
39
|
+
except Exception as e:
|
|
40
|
+
logger.error(ansi.style(f"Error listing targets: {str(e)}", fg=ansi.Fg.RED))
|
|
41
|
+
|
|
42
|
+
do_lst = do_list_targets
|
|
43
|
+
|
|
44
|
+
@cmd2.with_category('Target Commands')
|
|
45
|
+
def do_target_select(self, arg):
|
|
46
|
+
'Select a target from available targets'
|
|
47
|
+
try:
|
|
48
|
+
targets = self.target_manager.get_all_targets()
|
|
49
|
+
|
|
50
|
+
if not targets:
|
|
51
|
+
logger.info(ansi.style("No targets found in the database.", fg=ansi.Fg.YELLOW))
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
# Create list of target choices for display
|
|
55
|
+
# Show all targets with their type, and IP if available
|
|
56
|
+
target_choices = []
|
|
57
|
+
for t in targets:
|
|
58
|
+
ip_part = f" - {t['ip_address']}" if t.get('ip_address') else ""
|
|
59
|
+
target_choices.append(f"{t['name']} ({t['type']}){ip_part}")
|
|
60
|
+
|
|
61
|
+
# Use Input_Mgr for target selection
|
|
62
|
+
selected_choice = Input_Mgr.Instance().single_choice(
|
|
63
|
+
"Select target for operation:",
|
|
64
|
+
target_choices
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Find the index of the selected choice
|
|
68
|
+
selected_index = target_choices.index(selected_choice)
|
|
69
|
+
|
|
70
|
+
# Convert the selected target dictionary to a Vehicle instance using create_target_instance
|
|
71
|
+
selected_target_dict = targets[selected_index]
|
|
72
|
+
selected_target = self.target_manager.create_target_instance(selected_target_dict)
|
|
73
|
+
|
|
74
|
+
# Set the selected target as current
|
|
75
|
+
self.target_manager.set_current_target(selected_target)
|
|
76
|
+
|
|
77
|
+
logger.info(ansi.style(f"Selected target: {selected_target.name}", fg=ansi.Fg.GREEN))
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
logger.error(ansi.style(f"Error selecting target: {str(e)}", fg=ansi.Fg.RED))
|
|
81
|
+
|
|
82
|
+
@cmd2.with_category('Target Commands')
|
|
83
|
+
def do_edit_target(self, arg):
|
|
84
|
+
'Edit an existing target in the database'
|
|
85
|
+
try:
|
|
86
|
+
# Get all targets
|
|
87
|
+
targets = self.target_manager.get_all_targets()
|
|
88
|
+
if not targets:
|
|
89
|
+
logger.warning(ansi.style("No targets available to edit.", fg=ansi.Fg.YELLOW))
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
# Create list of target choices
|
|
93
|
+
target_choices = [f"{t['name']} ({t['target_id']})" for t in targets]
|
|
94
|
+
|
|
95
|
+
# Let user select a target
|
|
96
|
+
selected = Input_Mgr.Instance().single_choice(
|
|
97
|
+
"Select target to edit",
|
|
98
|
+
target_choices
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Get target ID from selection (take the last parentheses group)
|
|
102
|
+
target_id = selected.split('(')[-1].split(')')[0]
|
|
103
|
+
target = next(t for t in targets if t['target_id'] == target_id)
|
|
104
|
+
|
|
105
|
+
# Fields that can be edited
|
|
106
|
+
editable_fields = {
|
|
107
|
+
'name': str,
|
|
108
|
+
'status': str,
|
|
109
|
+
'ip_address': str,
|
|
110
|
+
'location': str
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
# Let user select which field to edit
|
|
114
|
+
field_choices = list(editable_fields.keys()) + ['properties']
|
|
115
|
+
field = Input_Mgr.Instance().single_choice(
|
|
116
|
+
"Select field to edit",
|
|
117
|
+
field_choices
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if field == 'properties':
|
|
121
|
+
# Handle properties editing
|
|
122
|
+
print("\nCurrent properties:")
|
|
123
|
+
for key, value in target['properties'].items():
|
|
124
|
+
print(f"{key}: {value}")
|
|
125
|
+
|
|
126
|
+
# Let user choose to add/edit/delete property
|
|
127
|
+
action = Input_Mgr.Instance().single_choice(
|
|
128
|
+
"Select action",
|
|
129
|
+
['Add property', 'Edit property', 'Delete property']
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if action == 'Add property':
|
|
133
|
+
key = Input_Mgr.Instance().string_input("Enter property name")
|
|
134
|
+
value = Input_Mgr.Instance().string_input("Enter property value")
|
|
135
|
+
target['properties'][key] = value
|
|
136
|
+
|
|
137
|
+
elif action == 'Edit property':
|
|
138
|
+
if not target['properties']:
|
|
139
|
+
logger.warning(ansi.style("No properties to edit.", fg=ansi.Fg.YELLOW))
|
|
140
|
+
return
|
|
141
|
+
prop_key = Input_Mgr.Instance().single_choice(
|
|
142
|
+
"Select property to edit",
|
|
143
|
+
list(target['properties'].keys())
|
|
144
|
+
)
|
|
145
|
+
new_value = Input_Mgr.Instance().string_input(
|
|
146
|
+
f"Enter new value for {prop_key}"
|
|
147
|
+
)
|
|
148
|
+
target['properties'][prop_key] = new_value
|
|
149
|
+
|
|
150
|
+
elif action == 'Delete property':
|
|
151
|
+
if not target['properties']:
|
|
152
|
+
logger.warning(ansi.style("No properties to delete.", fg=ansi.Fg.YELLOW))
|
|
153
|
+
return
|
|
154
|
+
prop_key = Input_Mgr.Instance().single_choice(
|
|
155
|
+
"Select property to delete",
|
|
156
|
+
list(target['properties'].keys())
|
|
157
|
+
)
|
|
158
|
+
del target['properties'][prop_key]
|
|
159
|
+
|
|
160
|
+
else:
|
|
161
|
+
# Handle regular field editing
|
|
162
|
+
current_value = target.get(field, '')
|
|
163
|
+
new_value = Input_Mgr.Instance().string_input(
|
|
164
|
+
f"Enter new value for {field}"
|
|
165
|
+
)
|
|
166
|
+
target[field] = new_value
|
|
167
|
+
|
|
168
|
+
# Update the target in the database
|
|
169
|
+
success = self.target_manager.update_target(target)
|
|
170
|
+
|
|
171
|
+
if success:
|
|
172
|
+
logger.info(ansi.style(f"Successfully updated target {target_id}", fg=ansi.Fg.GREEN))
|
|
173
|
+
else:
|
|
174
|
+
logger.error(ansi.style(f"Failed to update target {target_id}", fg=ansi.Fg.RED))
|
|
175
|
+
|
|
176
|
+
except Exception as e:
|
|
177
|
+
logger.error(ansi.style(f"Error editing target: {str(e)}", fg=ansi.Fg.RED))
|
|
178
|
+
logger.debug("Detailed error:", exc_info=True)
|
|
179
|
+
|
|
180
|
+
do_et = do_edit_target
|
|
181
|
+
|
|
182
|
+
@cmd2.with_category('Target Commands')
|
|
183
|
+
def do_target_import(self, arg):
|
|
184
|
+
'Import targets from JSON file (optional, only when needed)'
|
|
185
|
+
try:
|
|
186
|
+
if not arg:
|
|
187
|
+
json_file = Input_Mgr.Instance().string_input(
|
|
188
|
+
"Enter JSON file path (default: conf/target.json)"
|
|
189
|
+
) or "conf/target.json"
|
|
190
|
+
else:
|
|
191
|
+
json_file = arg.strip()
|
|
192
|
+
|
|
193
|
+
# Check if file exists
|
|
194
|
+
import os
|
|
195
|
+
if not os.path.exists(json_file):
|
|
196
|
+
logger.error(ansi.style(f"File not found: {json_file}", fg=ansi.Fg.RED))
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
# Check existing targets
|
|
200
|
+
existing_targets = self.target_manager.get_all_targets()
|
|
201
|
+
if existing_targets:
|
|
202
|
+
logger.warning(ansi.style(f"Database already contains {len(existing_targets)} targets:", fg=ansi.Fg.YELLOW))
|
|
203
|
+
for target in existing_targets:
|
|
204
|
+
logger.info(f" - {target['name']} ({target['target_id']})")
|
|
205
|
+
|
|
206
|
+
overwrite = Input_Mgr.Instance().single_choice(
|
|
207
|
+
"How to handle existing targets?",
|
|
208
|
+
["Skip existing (recommended)", "Overwrite existing", "Cancel import"]
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if overwrite == "Cancel import":
|
|
212
|
+
logger.info("Import cancelled")
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
force_overwrite = (overwrite == "Overwrite existing")
|
|
216
|
+
else:
|
|
217
|
+
force_overwrite = False
|
|
218
|
+
|
|
219
|
+
# Import targets
|
|
220
|
+
self.target_manager.parse_and_set_target_from_json(json_file, force_overwrite)
|
|
221
|
+
logger.info(ansi.style(f"Import completed from {json_file}", fg=ansi.Fg.GREEN))
|
|
222
|
+
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.error(ansi.style(f"Error importing targets: {str(e)}", fg=ansi.Fg.RED))
|
|
225
|
+
|
|
226
|
+
@cmd2.with_category('Target Commands')
|
|
227
|
+
def do_target_export(self, arg):
|
|
228
|
+
'Export current database targets to JSON file'
|
|
229
|
+
try:
|
|
230
|
+
if not arg:
|
|
231
|
+
json_file = Input_Mgr.Instance().string_input(
|
|
232
|
+
"Enter export file path (default: conf/target_export.json)"
|
|
233
|
+
) or "conf/target_export.json"
|
|
234
|
+
else:
|
|
235
|
+
json_file = arg.strip()
|
|
236
|
+
|
|
237
|
+
# Export targets
|
|
238
|
+
success = self.target_manager.export_targets_to_json(json_file, backup_original=True)
|
|
239
|
+
|
|
240
|
+
if success:
|
|
241
|
+
logger.info(ansi.style(f"Successfully exported targets to {json_file}", fg=ansi.Fg.GREEN))
|
|
242
|
+
else:
|
|
243
|
+
logger.error(ansi.style("Failed to export targets", fg=ansi.Fg.RED))
|
|
244
|
+
|
|
245
|
+
except Exception as e:
|
|
246
|
+
logger.error(ansi.style(f"Error exporting targets: {str(e)}", fg=ansi.Fg.RED))
|