Send a command to an active Metasploit session (Meterpreter or Shell) and get output. Uses session.run_with_output for Meterpreter, and a prompt-aware loop for shells. The agent is responsible for parsing the raw output. In Meterpreter mode, to run a shell command, run `shell` to e
(
session_id: int,
command: str,
timeout_seconds: int = SESSION_COMMAND_TIMEOUT,
)
| 1233 | |
| 1234 | @mcp.tool() |
| 1235 | async def send_session_command( |
| 1236 | session_id: int, |
| 1237 | command: str, |
| 1238 | timeout_seconds: int = SESSION_COMMAND_TIMEOUT, |
| 1239 | ) -> Dict[str, Any]: |
| 1240 | """ |
| 1241 | Send a command to an active Metasploit session (Meterpreter or Shell) and get output. |
| 1242 | Uses session.run_with_output for Meterpreter, and a prompt-aware loop for shells. |
| 1243 | The agent is responsible for parsing the raw output. |
| 1244 | |
| 1245 | In Meterpreter mode, to run a shell command, run `shell` to enter the shell mode first. |
| 1246 | To exit shell mode and return to Meterpreter, run `exit`. |
| 1247 | |
| 1248 | Args: |
| 1249 | session_id: ID of the target session. |
| 1250 | command: Command string to execute in the session. |
| 1251 | timeout_seconds: Maximum time to wait for the command to complete. |
| 1252 | |
| 1253 | Returns: |
| 1254 | Dictionary with status ('success', 'error', 'timeout') and raw command output. |
| 1255 | """ |
| 1256 | client = get_msf_client() |
| 1257 | logger.info(f"Sending command to session {session_id}: '{command}'") |
| 1258 | session_id_str = str(session_id) |
| 1259 | |
| 1260 | try: |
| 1261 | # --- Get Session Info and Object --- |
| 1262 | current_sessions = await asyncio.to_thread(lambda: client.sessions.list) |
| 1263 | if session_id_str not in current_sessions: |
| 1264 | logger.error(f"Session {session_id} not found.") |
| 1265 | return {"status": "error", "message": f"Session {session_id} not found."} |
| 1266 | |
| 1267 | session_info = current_sessions[session_id_str] |
| 1268 | session_type = session_info.get('type', 'unknown').lower() if isinstance(session_info, dict) else 'unknown' |
| 1269 | logger.debug(f"Session {session_id} type: {session_type}") |
| 1270 | |
| 1271 | session = await asyncio.to_thread(lambda: client.sessions.session(session_id_str)) |
| 1272 | if not session: |
| 1273 | logger.error(f"Failed to get session object for existing session {session_id}.") |
| 1274 | return {"status": "error", "message": f"Error retrieving session {session_id} object."} |
| 1275 | |
| 1276 | # --- Execute Command Based on Type --- |
| 1277 | output = "" |
| 1278 | status = "error" # Default status |
| 1279 | message = "Command execution failed or type unknown." |
| 1280 | |
| 1281 | if session_type == 'meterpreter': |
| 1282 | if session_shell_type.get(session_id_str) is None: |
| 1283 | session_shell_type[session_id_str] = 'meterpreter' |
| 1284 | |
| 1285 | logger.debug(f"Using session.run_with_output for Meterpreter session {session_id}") |
| 1286 | try: |
| 1287 | # Use asyncio.wait_for to handle timeout manually since run_with_output doesn't support timeout parameter |
| 1288 | if command == "shell": |
| 1289 | if session_shell_type[session_id_str] == 'meterpreter': |
| 1290 | output = session.run_with_output(command, end_strs=['created.']) |
| 1291 | session_shell_type[session_id_str] = 'shell' |
| 1292 | session.read() # Clear buffer |