Run a Metasploit exploit module with specified options. Handles async (job) and sync (console) execution, and includes session polling for jobs. Args: module_name: Name/path of the exploit module (e.g., 'unix/ftp/vsftpd_234_backdoor'). options: Dictionary of exploit mod
(
module_name: str,
options: Dict[str, Any],
payload_name: Optional[str] = None,
payload_options: Optional[Union[Dict[str, Any], str]] = None,
run_as_job: bool = False,
check_vulnerability: bool = False, # New option
timeout_seconds: int = LONG_CONSOLE_READ_TIMEOUT # Used only if run_as_job=False
)
| 990 | |
| 991 | @mcp.tool() |
| 992 | async def run_exploit( |
| 993 | module_name: str, |
| 994 | options: Dict[str, Any], |
| 995 | payload_name: Optional[str] = None, |
| 996 | payload_options: Optional[Union[Dict[str, Any], str]] = None, |
| 997 | run_as_job: bool = False, |
| 998 | check_vulnerability: bool = False, # New option |
| 999 | timeout_seconds: int = LONG_CONSOLE_READ_TIMEOUT # Used only if run_as_job=False |
| 1000 | ) -> Dict[str, Any]: |
| 1001 | """ |
| 1002 | Run a Metasploit exploit module with specified options. Handles async (job) |
| 1003 | and sync (console) execution, and includes session polling for jobs. |
| 1004 | |
| 1005 | Args: |
| 1006 | module_name: Name/path of the exploit module (e.g., 'unix/ftp/vsftpd_234_backdoor'). |
| 1007 | options: Dictionary of exploit module options (e.g., {'RHOSTS': '192.168.1.1'}). |
| 1008 | payload_name: Name of the payload (e.g., 'linux/x86/meterpreter/reverse_tcp'). |
| 1009 | payload_options: Dictionary of payload options (e.g., {'LHOST': '...', 'LPORT': ...}) |
| 1010 | or string format "LHOST=1.2.3.4,LPORT=4444". Prefer dict format. |
| 1011 | run_as_job: If False (default), run sync via console. If True, run async via RPC. |
| 1012 | check_vulnerability: If True, run module's 'check' action first (if available). |
| 1013 | timeout_seconds: Max time for synchronous run via console. |
| 1014 | |
| 1015 | Returns: |
| 1016 | Dictionary with execution results (job_id, session_id, output) or error details. |
| 1017 | """ |
| 1018 | logger.info(f"Request to run exploit '{module_name}'. Job: {run_as_job}, Check: {check_vulnerability}, Payload: {payload_name}") |
| 1019 | |
| 1020 | # Parse payload options gracefully |
| 1021 | try: |
| 1022 | parsed_payload_options = _parse_options_gracefully(payload_options) |
| 1023 | except ValueError as e: |
| 1024 | return {"status": "error", "message": f"Invalid payload_options format: {e}"} |
| 1025 | |
| 1026 | payload_spec = None |
| 1027 | if payload_name: |
| 1028 | payload_spec = {"name": payload_name, "options": parsed_payload_options} |
| 1029 | |
| 1030 | if check_vulnerability: |
| 1031 | logger.info(f"Performing vulnerability check first for {module_name}...") |
| 1032 | try: |
| 1033 | # Use the console helper for 'check' as it provides output |
| 1034 | check_result = await _execute_module_console( |
| 1035 | module_type='exploit', |
| 1036 | module_name=module_name, |
| 1037 | module_options=options, |
| 1038 | command='check', # Use the 'check' command |
| 1039 | timeout=timeout_seconds |
| 1040 | ) |
| 1041 | logger.info(f"Vulnerability check result: {check_result.get('status')} - {check_result.get('message')}") |
| 1042 | output = check_result.get("module_output", "").lower() |
| 1043 | # Check output for positive indicators |
| 1044 | is_vulnerable = "appears vulnerable" in output or "is vulnerable" in output or "+ vulnerable" in output |
| 1045 | # Check for negative indicators (more reliable sometimes) |
| 1046 | is_not_vulnerable = "does not appear vulnerable" in output or "is not vulnerable" in output or "target is not vulnerable" in output or "check failed" in output |
| 1047 | if check_result.get('status') == "errror": |
| 1048 | logger.warning(f"Error from metasploit: {check_result}") |
| 1049 | return {"status": "aborted", "message": f"Check indicates a failure: {check_result.get('message')}", "check_output": check_result.get("module_output")} |