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.
@@ -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))